Repository: Ameliorated-LLC/trusted-uninstaller-cli Branch: public Commit: f325e9d950e6 Files: 139 Total size: 2.4 MB Directory structure: gitextract_dcyh09z4/ ├── .gitea/ │ └── workflows/ │ └── github_mirror.yaml ├── .gitignore ├── Core/ │ ├── Actions/ │ │ ├── CmdAction.cs │ │ ├── CoreActions.cs │ │ ├── RegistryKeyAction.cs │ │ ├── RegistryValueAction.cs │ │ ├── RunAction.cs │ │ ├── ServiceAction.cs │ │ ├── TaskKillAction.cs │ │ └── UninstallTaskStatus.cs │ ├── Core.projitems │ ├── Core.shproj │ ├── Core.shproj.DotSettings │ ├── Exceptions/ │ │ └── UnexpectedException.cs │ ├── Helper/ │ │ ├── Interop/ │ │ │ └── Helper.cs │ │ ├── client-helper.sln │ │ ├── client-helper.vcxproj │ │ ├── client-helper.vcxproj.filters │ │ ├── dllmain.cpp │ │ ├── framework.h │ │ ├── pch.cpp │ │ └── pch.h │ ├── Miscellaneous/ │ │ ├── Serializables.cs │ │ ├── StreamReaderWithPosition.cs │ │ └── XmlDeserializer.cs │ ├── Services/ │ │ ├── Config.cs │ │ ├── Log.cs │ │ └── Output.cs │ └── Utilities/ │ ├── IdManagers.cs │ ├── StringUtils.cs │ └── Wrap.cs ├── Interprocess/ │ ├── InterLink.cs │ ├── Interprocess.projitems │ ├── Interprocess.shproj │ ├── JsonSerializables.cs │ ├── LevelController.cs │ ├── Levels.cs │ ├── Serialization.cs │ ├── SynchronousIOCanceler.cs │ └── Threads.cs ├── LICENSE.md ├── ManagedWimLib/ │ ├── Compressors/ │ │ ├── Compressor.cs │ │ └── Decompressor.cs │ ├── Helper.cs │ ├── IterateCallback.cs │ ├── ManagedWimLib.csproj │ ├── ManagedWimLib.netfx.targets │ ├── NUGET_README.md │ ├── NativeStructs.cs │ ├── NugetPack.cmd │ ├── ProgressCallback.cs │ ├── WimLibException.cs │ ├── WimLibLoadManager.cs │ ├── WimLibLoader.cs │ └── WimStruct.cs ├── README.md ├── TrustedUninstaller.CLI/ │ ├── CLI.cs │ ├── CommandLine.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── TrustedUninstaller.CLI.csproj │ └── app.manifest ├── TrustedUninstaller.Shared/ │ ├── Actions/ │ │ ├── AppxAction.cs │ │ ├── CmdAction.cs │ │ ├── DownloadAction.cs │ │ ├── FileAction.cs │ │ ├── LanguageAction.cs │ │ ├── LineInFileAction.cs │ │ ├── PowershellAction.cs │ │ ├── RegistryKeyAction.cs │ │ ├── RegistryValueAction.cs │ │ ├── RunAction.cs │ │ ├── ScheduledTaskAction.cs │ │ ├── ServiceAction.cs │ │ ├── ShortcutAction.cs │ │ ├── SoftwareAction.cs │ │ ├── SystemPackageAction.cs │ │ ├── TaskAction.cs │ │ ├── TaskKillAction.cs │ │ ├── UpdateAction.cs │ │ ├── UserAction.cs │ │ └── WriteStatusAction.cs │ ├── AmeliorationUtil.cs │ ├── AugmentedProcess.cs │ ├── ControlWriter.cs │ ├── Defender.cs │ ├── Exceptions/ │ │ ├── ErrorHandlingException.cs │ │ ├── InvalidRegistryEntryException.cs │ │ └── TaskInProgressException.cs │ ├── Globals.cs │ ├── ISO.cs │ ├── NativeProcess.cs │ ├── NtStatus.cs │ ├── OOBE.cs │ ├── Parser/ │ │ ├── PlaybookParser.cs │ │ └── TaskActionResolver.cs │ ├── Playbook.cs │ ├── Predicates/ │ │ └── IPredicate.cs │ ├── ProcessPrivilege.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ ├── uefi-ntfs-ame-old.img │ │ └── uefi-ntfs-ame.img │ ├── ProviderStatus.cs │ ├── Requirements.cs │ ├── Tasks/ │ │ ├── ITaskAction.cs │ │ ├── OutputProcessor.cs │ │ ├── TaskAction.cs │ │ ├── TaskList.cs │ │ ├── UninstallTask.cs │ │ ├── UninstallTaskPrivilege.cs │ │ └── UninstallTaskStatus.cs │ ├── TrustedUninstaller.Shared.csproj │ ├── USB/ │ │ ├── BZip2.cs │ │ ├── Drive.cs │ │ ├── Drivers.cs │ │ ├── Format.cs │ │ ├── ISO.cs │ │ ├── ISOWIM.cs │ │ ├── InterMethods.cs │ │ ├── Interop.cs │ │ ├── OSDownload.cs │ │ └── USB.cs │ ├── WimLib/ │ │ ├── Compressors/ │ │ │ ├── Compressor.cs │ │ │ └── Decompressor.cs │ │ ├── DynLoader/ │ │ │ ├── DynLoaderBase.cs │ │ │ ├── LoadManagerBase.cs │ │ │ ├── NativeMethods.cs │ │ │ ├── PlatformConvention.cs │ │ │ └── SafeLibHandle.cs │ │ ├── Helper.cs │ │ ├── IterateCallback.cs │ │ ├── NativeStructs.cs │ │ ├── ProgressCallback.cs │ │ ├── WimLibException.cs │ │ ├── WimLibLoadManager.cs │ │ ├── WimLibLoader.cs │ │ └── WimStruct.cs │ ├── WimWrapper.cs │ └── WinUtil.cs └── TrustedUninstaller.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitea/workflows/github_mirror.yaml ================================================ name: GitHub Mirror on: push: branches: - public jobs: mirror_to_github: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: Mirror to GitHub env: GITHUBTOKEN: ${{ secrets.GITHUBTOKEN }} run: | git checkout public git reset --hard origin/public git push --force --tags https://${GITHUBTOKEN}@github.com/Ameliorated-LLC/trusted-uninstaller-cli public:public ================================================ FILE: .gitignore ================================================ # AME GUI Resource Playbooks TrustedUninstaller.GUI/resources/Shared-PB/* TrustedUninstaller.GUI/resources/W10-PB/* TrustedUninstaller.GUI/resources/W11-PB/* # GUI Builder Temp Files TrustedUninstaller.GUI/gui-builder/Compiled.txt TrustedUninstaller.GUI/gui-builder/BuildOut-* !*.gitkeep # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.tlog *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Nuget personal access tokens and Credentials nuget.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd # VS Code files for those working on multiple tools .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json *.code-workspace # Local History for Visual Studio Code .history/ # Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp # JetBrains Rider .idea/ *.sln.iml ### Git ### # Created by git for backups. To disable backups in Git: # $ git config --global mergetool.keepBackup false *.orig # Created by git when using merge tools for conflicts *.BACKUP.* *.BASE.* *.LOCAL.* *.REMOTE.* *_BACKUP_*.txt *_BASE_*.txt *_LOCAL_*.txt *_REMOTE_*.txt ### vs ### # User-specific files # User-specific files (MonoDevelop/Xamarin Studio) # Mono auto generated files # Build results # Visual Studio 2015/2017 cache/options directory # Uncomment if you have tasks that create the project's static files in wwwroot # Visual Studio 2017 auto generated files # MSTest test Results # NUnit # Build Results of an ATL Project # Benchmark Results # .NET Core # StyleCop # Files built by Visual Studio # Chutzpah Test files # Visual C++ cache files # Visual Studio profiler # Visual Studio Trace Files # TFS 2012 Local Workspace # Guidance Automation Toolkit # ReSharper is a .NET coding add-in # TeamCity is a build add-in # DotCover is a Code Coverage Tool # AxoCover is a Code Coverage Tool # Coverlet is a free, cross platform Code Coverage Tool coverage*[.json, .xml, .info] # Visual Studio code coverage results # NCrunch # MightyMoose # Web workbench (sass) # Installshield output folder # DocProject is a documentation generator add-in # Click-Once directory # Publish Web Output # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted # NuGet Packages # NuGet Symbol Packages # The packages folder can be ignored because of Package Restore # except build/, which is used as an MSBuild target. # Uncomment if necessary however generally it will be regenerated when needed # NuGet v3's project.json files produces more ignorable files # Microsoft Azure Build Output # Microsoft Azure Emulator # Windows Store app package directories and files # Visual Studio cache files # files ending in .cache can be ignored # but keep track of directories ending in .cache # Others # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) # RIA/Silverlight projects # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) # SQL Server files # Business Intelligence projects # Microsoft Fakes # GhostDoc plugin setting file # Node.js Tools for Visual Studio # Visual Studio 6 build log # Visual Studio 6 workspace options file # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) # Visual Studio LightSwitch build output # Paket dependency manager # FAKE - F# Make # CodeRush personal settings # Python Tools for Visual Studio (PTVS) # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio # Telerik's JustMock configuration file # BizTalk build output # OpenCover UI analysis results # Azure Stream Analytics local run output # MSBuild Binary and Structured Log # NVidia Nsight GPU debugger configuration file # MFractors (Xamarin productivity tool) working folder # Local History for Visual Studio # BeatPulse healthcheck temp database # Backup folder for Package Reference Convert tool in Visual Studio 2017 # Ionide (cross platform F# VS Code tools) working folder ================================================ FILE: Core/Actions/CmdAction.cs ================================================ using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Core.Exceptions; using YamlDotNet.Serialization; namespace Core.Actions { public class CmdAction : ICoreAction { [YamlMember(typeof(string), Alias = "command")] public string Command { get; set; } [YamlMember(typeof(string), Alias = "timeout")] public int? Timeout { get; set; } [YamlMember(typeof(string), Alias = "wait")] public bool Wait { get; set; } = true; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; private int? ExitCode { get; set; } public string? StandardError { get; set; } public string StandardOutput { get; set; } public string ErrorString() => $"CmdAction failed to run command '{Command}'."; public UninstallTaskStatus GetStatus() { if (InProgress) { return UninstallTaskStatus.InProgress; } return ExitCode == null ? UninstallTaskStatus.ToDo: UninstallTaskStatus.Completed; } public void RunTask(bool logExceptions = true) { ExitCode = null; RunAsProcess(); } private void RunAsProcess() { var process = new Process(); var startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Normal, FileName = "cmd.exe", Arguments = "/C " + $"\"{this.Command}\"", UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, CreateNoWindow = true }; if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } process.StartInfo = startInfo; process.Start(); if (!Wait) { process.Dispose(); return; } 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(); process.BeginErrorReadLine(); if (Timeout != null) { var exited = process.WaitForExit(Timeout.Value); if (!exited) { process.Kill(); throw new TimeoutException($"Command '{Command}' timeout exceeded."); } } else { bool exited = process.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && CmdRunning(process.Id)) { exited = process.WaitForExit(30000); } } int exitCode = 0; try { exitCode = process.ExitCode; } catch (Exception ex) { } if (exitCode != 0) { StandardError = error.ToString(); this.ExitCode = exitCode; } else { ExitCode = 0; } process.CancelOutputRead(); process.CancelErrorRead(); process.Dispose(); } private static bool CmdRunning(int id) { try { return Process.GetProcessesByName("cmd").Any(x => x.Id == id); } catch (Exception) { return false; } } private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { } } } ================================================ FILE: Core/Actions/CoreActions.cs ================================================ using System.Threading.Tasks; namespace Core.Actions { public interface ICoreAction { public void RunTask(bool logExceptions); } public static class CoreActions { public static void SafeRun(ICoreAction action, bool logExceptions = false) { Wrap.ExecuteSafe(() => action.RunTask(logExceptions), logExceptions); } } } ================================================ FILE: Core/Actions/RegistryKeyAction.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; using Core.Exceptions; using YamlDotNet.Serialization; namespace Core.Actions { public enum RegistryKeyOperation { Delete = 0, Add = 1 } public class RegistryKeyAction : ICoreAction { public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } [YamlMember(typeof(RegistryKeyOperation), Alias = "operation")] public RegistryKeyOperation Operation { get; set; } = RegistryKeyOperation.Delete; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; static Dictionary HiveKeys = new Dictionary { { RegistryHive.ClassesRoot, new UIntPtr(0x80000000u) }, { RegistryHive.CurrentConfig, new UIntPtr(0x80000005u) }, { RegistryHive.CurrentUser, new UIntPtr(0x80000001u) }, //{ RegistryHive.DynData, new UIntPtr(0x80000006u) }, { RegistryHive.LocalMachine, new UIntPtr(0x80000002u) }, { RegistryHive.PerformanceData, new UIntPtr(0x80000004u) }, { RegistryHive.Users, new UIntPtr(0x80000003u) } }; [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult); [DllImport("advapi32.dll", EntryPoint = "RegDeleteKeyEx", SetLastError = true)] private static extern int RegDeleteKeyEx( UIntPtr hKey, string lpSubKey, uint samDesired, // see Notes below uint Reserved); private static void DeleteKeyTreeWin32(string key, RegistryHive hive) { var openedKey = RegistryKey.OpenBaseKey(hive, RegistryView.Default).OpenSubKey(key); if (openedKey == null) return; openedKey.GetSubKeyNames().ToList().ForEach(subKey => DeleteKeyTreeWin32(key + "\\" + subKey, hive)); openedKey.Close(); RegDeleteKeyEx(HiveKeys[hive], key, 0x0100, 0); } private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"RegistryKeyAction failed to {Operation.ToString().ToLower()} key '{KeyName}'."; private List GetRoots() { var hive = KeyName.Split('\\').GetValue(0).ToString().ToUpper(); var list = new List(); list.Add(hive switch { "HKCU" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKEY_CURRENT_USER" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKLM" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKEY_LOCAL_MACHINE" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKCR" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKEY_CLASSES_ROOT" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKU" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), "HKEY_USERS" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), _ => throw new ArgumentException($"Key '{KeyName}' does not specify a valid registry hive.") }); return list; } public string GetSubKey() => KeyName.Substring(KeyName.IndexOf('\\') + 1); private RegistryKey? OpenSubKey(RegistryKey root) { var subKeyPath = GetSubKey(); if (subKeyPath == null) throw new ArgumentException($"Key '{KeyName}' is invalid."); return root.OpenSubKey(subKeyPath, true); } public UninstallTaskStatus GetStatus() { try { var roots = GetRoots(); foreach (var _root in roots) { var root = _root; var subKey = GetSubKey(); var openedSubKey = root.OpenSubKey(subKey); if (Operation == RegistryKeyOperation.Delete && openedSubKey != null) { return UninstallTaskStatus.ToDo; } if (Operation == RegistryKeyOperation.Add && openedSubKey == null) { return UninstallTaskStatus.ToDo; } } } catch (Exception e) { Log.EnqueueExceptionSafe(e); return UninstallTaskStatus.ToDo; } return UninstallTaskStatus.Completed; } public void RunTask(bool logExceptions = true) { var roots = GetRoots(); foreach (var _root in roots) { var root = _root; var subKey = GetSubKey(); try { if (Operation == RegistryKeyOperation.Add && root.OpenSubKey(subKey) == null) { root.CreateSubKey(subKey); } if (Operation == RegistryKeyOperation.Delete) { try { root.DeleteSubKeyTree(subKey, false); } catch (Exception e) { Log.EnqueueExceptionSafe(LogType.Warning, e, ("Key", root?.Name + "\\" + subKey)); var rootHive = root.Name.Split('\\').First() switch { "HKEY_CURRENT_USER" => RegistryHive.CurrentUser, "HKEY_LOCAL_MACHINE" => RegistryHive.LocalMachine, "HKEY_CLASSES_ROOT" => RegistryHive.ClassesRoot, "HKEY_USERS" => RegistryHive.Users, _ => throw new ArgumentException($"Unable to parse: " + root.Name.Split('\\').First()) }; DeleteKeyTreeWin32(root.Name.StartsWith("HKEY_USERS") ? root.Name.Split('\\')[1] + "\\" + subKey: subKey, rootHive); } } } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, ("Key", root?.Name + "\\" + subKey)); } } } } } ================================================ FILE: Core/Actions/RegistryValueAction.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; using Core.Exceptions; using YamlDotNet.Serialization; namespace Core.Actions { public enum RegistryValueOperation { Delete = 0, Add = 1, // This indicates to skip the action if the specified value does not already exist Set = 2 } public enum RegistryValueType { REG_SZ = RegistryValueKind.String, REG_MULTI_SZ = RegistryValueKind.MultiString, REG_EXPAND_SZ = RegistryValueKind.ExpandString, REG_DWORD = RegistryValueKind.DWord, REG_QWORD = RegistryValueKind.QWord, REG_BINARY = RegistryValueKind.Binary, REG_NONE = RegistryValueKind.None, REG_UNKNOWN = RegistryValueKind.Unknown } public class RegistryValueAction : ICoreAction { [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } [YamlMember(typeof(string), Alias = "value")] public string Value { get; set; } = ""; [YamlMember(typeof(object), Alias = "data")] public object? Data { get; set; } [YamlMember(typeof(RegistryValueType), Alias = "type")] public RegistryValueType Type { get; set; } [YamlMember(typeof(RegistryValueOperation), Alias = "operation")] public RegistryValueOperation Operation { get; set; } = RegistryValueOperation.Add; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() { /* int roots; try { roots = GetRoots().Count; } catch (Exception e) { roots = 1; } */ return ProgressWeight; } private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"RegistryValueAction failed to {Operation.ToString().ToLower()} value '{Value}' in key '{KeyName}'"; private List GetRoots() { var hive = KeyName.Split('\\').GetValue(0).ToString().ToUpper(); var list = new List(); list.Add(hive switch { "HKCU" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKEY_CURRENT_USER" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKLM" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKEY_LOCAL_MACHINE" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKCR" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKEY_CLASSES_ROOT" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKU" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), "HKEY_USERS" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), _ => throw new ArgumentException($"Key '{KeyName}' does not specify a valid registry hive.") }); return list; } public string GetSubKey() => KeyName.Substring(KeyName.IndexOf('\\') + 1); private RegistryKey? OpenSubKey(RegistryKey root) { var subKeyPath = GetSubKey(); if (subKeyPath == null) throw new ArgumentException($"Key '{KeyName}' is invalid."); return root.OpenSubKey(subKeyPath, true); } public object? GetCurrentValue(RegistryKey root) { var subkey = GetSubKey(); return Registry.GetValue(root.Name + "\\" + subkey, Value, null); } public static byte[] StringToByteArray(string hex) { return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .ToArray(); } public UninstallTaskStatus GetStatus() { try { var roots = GetRoots(); foreach (var _root in roots) { var root = _root; var subKey = GetSubKey(); var openedSubKey = root.OpenSubKey(subKey); if (openedSubKey == null && (Operation == RegistryValueOperation.Set || Operation == RegistryValueOperation.Delete)) continue; if (openedSubKey == null) return UninstallTaskStatus.ToDo; var value = openedSubKey.GetValue(Value); if (value == null) { if (Operation == RegistryValueOperation.Set || Operation == RegistryValueOperation.Delete) continue; return UninstallTaskStatus.ToDo; } if (Operation == RegistryValueOperation.Delete) return UninstallTaskStatus.ToDo; if (Data == null) return UninstallTaskStatus.ToDo; bool matches; try { matches = Type switch { RegistryValueType.REG_SZ => Data.ToString() == value.ToString(), RegistryValueType.REG_EXPAND_SZ => // RegistryValueOptions.DoNotExpandEnvironmentNames above did not seem to work. Environment.ExpandEnvironmentVariables(Data.ToString()) == value.ToString(), RegistryValueType.REG_MULTI_SZ => Data.ToString() == "" ? ((string[])value).SequenceEqual(new string[] { }) : ((string[])value).SequenceEqual(Data.ToString().Split(new string[] { "\\0" }, StringSplitOptions.None)), RegistryValueType.REG_DWORD => unchecked((int)Convert.ToUInt32(Data)) == (int)value, RegistryValueType.REG_QWORD => Convert.ToUInt64(Data) == (ulong)value, RegistryValueType.REG_BINARY => ((byte[])value).SequenceEqual(StringToByteArray(Data.ToString())), RegistryValueType.REG_NONE => ((byte[])value).SequenceEqual(new byte[0]), RegistryValueType.REG_UNKNOWN => Data.ToString() == value.ToString(), _ => throw new ArgumentException("Impossible.") }; } catch (InvalidCastException) { matches = false; } if (!matches) return UninstallTaskStatus.ToDo; } } catch (Exception e) { Log.EnqueueExceptionSafe(e); return UninstallTaskStatus.ToDo; } return UninstallTaskStatus.Completed; } public void RunTask(bool logExceptions = true) { var roots = GetRoots(); foreach (var _root in roots) { var root = _root; var subKey = GetSubKey(); try { if (GetCurrentValue(root) == Data) continue; if (root.OpenSubKey(subKey) == null && Operation == RegistryValueOperation.Set) continue; if (root.OpenSubKey(subKey) == null && Operation == RegistryValueOperation.Add) root.CreateSubKey(subKey); if (Operation == RegistryValueOperation.Delete) { var key = root.OpenSubKey(subKey, true); key?.DeleteValue(Value); continue; } if (Type == RegistryValueType.REG_BINARY) { var data = StringToByteArray(Data.ToString()); Registry.SetValue(root.Name + "\\" + subKey, Value, data, (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_DWORD) { // DWORD values using the highest bit set fail without this, for example '2962489444'. // See https://stackoverflow.com/questions/6608400/how-to-put-a-dword-in-the-registry-with-the-highest-bit-set; var value = unchecked((int)Convert.ToUInt32(Data)); Registry.SetValue(root.Name + "\\" + subKey, Value, value, (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_QWORD) { Registry.SetValue(root.Name + "\\" + subKey, Value, Convert.ToUInt64(Data), (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_NONE) { byte[] none = new byte[0]; Registry.SetValue(root.Name + "\\" + subKey, Value, none, (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_MULTI_SZ) { string[] data; if (Data.ToString() == "") data = new string[] { }; else data = Data.ToString().Split(new string[] { "\\0" }, StringSplitOptions.None); Registry.SetValue(root.Name + "\\" + subKey, Value, data, (RegistryValueKind)Type); } else { Registry.SetValue(root.Name + "\\" + subKey, Value, Data, (RegistryValueKind)Type); } } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, ("Key", root?.Name + "\\" + subKey), ("Value", Value)); } } return; } } } ================================================ FILE: Core/Actions/RunAction.cs ================================================ using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using YamlDotNet.Serialization; namespace Core.Actions { public class RunAction : ICoreAction { [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } = null; [YamlMember(typeof(string), Alias = "exe")] public string Exe { get; set; } [YamlMember(typeof(string), Alias = "args")] public string? Arguments { get; set; } [YamlMember(typeof(bool), Alias = "baseDir")] public bool BaseDir { get; set; } = false; [YamlMember(typeof(bool), Alias = "exeDir")] public bool ExeDir { get; set; } = false; [YamlMember(typeof(bool), Alias = "createWindow")] public bool CreateWindow { get; set; } = false; [YamlMember(typeof(bool), Alias = "showOutput")] public bool ShowOutput { get; set; } = true; [YamlMember(typeof(bool), Alias = "showError")] public bool ShowError { get; set; } = true; [YamlMember(typeof(int), Alias = "timeout")] public int? Timeout { get; set; } [YamlMember(typeof(string), Alias = "wait")] public bool Wait { get; set; } = true; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 5; public int GetProgressWeight() => ProgressWeight; private bool InProgress { get; set; } = false; public void ResetProgress() => InProgress = false; private bool HasExited { get; set; } = false; //public int ExitCode { get; set; } public string? Output { get; private set; } private string? StandardError { get; set; } public string ErrorString() => String.IsNullOrEmpty(Arguments) ? $"RunAction failed to execute '{Exe}'." : $"RunAction failed to execute '{Exe}' with arguments '{Arguments}'."; public static bool ExistsInPath(string fileName) { if (File.Exists(fileName)) return true; var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) { var fullPath = Path.Combine(path, fileName); if (File.Exists(fullPath)) return true; if (!fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath + ".exe")) return true; } return false; } public UninstallTaskStatus GetStatus() { if (InProgress) { return UninstallTaskStatus.InProgress; } return HasExited || !Wait ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } public void RunTask(bool logExceptions = true) { if (RawPath != null) RawPath = Environment.ExpandEnvironmentVariables(RawPath); InProgress = true; var currentDir = Directory.GetCurrentDirectory(); if (BaseDir) RawPath = currentDir; string file = null; if (RawPath != null && File.Exists(Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe))) file = Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe); else if (ExistsInPath(Exe) || File.Exists(Environment.ExpandEnvironmentVariables(Exe))) file = Environment.ExpandEnvironmentVariables(Exe); if (file == null) throw new FileNotFoundException($"Executable not found."); RunAsProcess(file); InProgress = false; return; } private void RunAsProcess(string file) { var startInfo = new ProcessStartInfo { CreateNoWindow = !this.CreateWindow, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = true, RedirectStandardOutput = true, FileName = file, }; if (Arguments != null) startInfo.Arguments = Environment.ExpandEnvironmentVariables(Arguments); if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } if (!ShowOutput) startInfo.RedirectStandardOutput = false; if (!ShowError) startInfo.RedirectStandardError = false; var exeProcess = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; exeProcess.Start(); if (!Wait) { exeProcess.Dispose(); return; } if (ShowOutput) exeProcess.OutputDataReceived += ProcOutputHandler; if (ShowError) exeProcess.ErrorDataReceived += ProcOutputHandler; if (ShowOutput) exeProcess.BeginOutputReadLine(); if (ShowError) exeProcess.BeginErrorReadLine(); if (Timeout.HasValue) { var exited = exeProcess.WaitForExit(Timeout.Value); if (!exited) { exeProcess.Kill(); throw new TimeoutException($"Executable run timeout exceeded."); } } else { bool exited = exeProcess.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(exeProcess.ProcessName, exeProcess.Id)) { exited = exeProcess.WaitForExit(30000); } } HasExited = true; if (ShowOutput) exeProcess.CancelOutputRead(); if (ShowError) exeProcess.CancelErrorRead(); exeProcess.Dispose(); } private static bool ExeRunning(string name, int id) { try { return Process.GetProcessesByName(name).Any(x => x.Id == id); } catch (Exception) { return false; } } private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { } } } ================================================ FILE: Core/Actions/ServiceAction.cs ================================================ #nullable enable using System; using System.Collections.Specialized; using System.Configuration.Install; using System.Diagnostics; using System.IO; using System.Linq; using System.Management; using System.ServiceProcess; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; using Core.Exceptions; using YamlDotNet.Serialization; using Core; namespace Core.Actions { public enum ServiceOperation { Stop, Continue, Start, Pause, Delete, Change } public class ServiceAction : ICoreAction { public void RunTaskOnMainThread() { throw new NotImplementedException(); } [YamlMember(typeof(ServiceOperation), Alias = "operation")] public ServiceOperation Operation { get; set; } = ServiceOperation.Delete; [YamlMember(typeof(string), Alias = "name")] public string ServiceName { get; set; } = null!; [YamlMember(typeof(int), Alias = "startup")] public int? Startup { get; set; } [YamlMember(typeof(bool), Alias = "deleteStop")] public bool DeleteStop { get; set; } = true; [YamlMember(typeof(bool), Alias = "deleteUsingRegistry")] public bool RegistryDelete { get; set; } = false; [YamlMember(typeof(string), Alias = "device")] public bool Device { get; set; } = false; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 4; public int GetProgressWeight() => ProgressWeight; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"ServiceAction failed to {Operation.ToString().ToLower()} service {ServiceName}."; private ServiceController? GetService() { if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0); if (ServiceName.EndsWith("*")) return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)); if (ServiceName.StartsWith("*")) return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)); return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase)); } private ServiceController? GetDevice() { if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0); if (ServiceName.EndsWith("*")) return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)); if (ServiceName.StartsWith("*")) return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)); return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase)); } public UninstallTaskStatus GetStatus() { if (InProgress) return UninstallTaskStatus.InProgress; if (Operation == ServiceOperation.Change && Startup.HasValue) { // TODO: Implement dev log. Example: // if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry."); var root = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}"); if (root == null) return UninstallTaskStatus.Completed; var value = root.GetValue("Start"); return (int)value == Startup.Value ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } ServiceController? serviceController; if (Device) serviceController = GetDevice(); else serviceController = GetService(); if (Operation == ServiceOperation.Delete && RegistryDelete) { // TODO: Implement dev log. Example: // if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry."); var root = Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}"); return root == null ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } return Operation switch { ServiceOperation.Stop => serviceController == null || serviceController?.Status == ServiceControllerStatus.Stopped || serviceController?.Status == ServiceControllerStatus.StopPending ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Continue => serviceController == null || serviceController?.Status == ServiceControllerStatus.Running || serviceController?.Status == ServiceControllerStatus.ContinuePending ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Start => serviceController?.Status == ServiceControllerStatus.StartPending || serviceController?.Status == ServiceControllerStatus.Running ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Pause => serviceController == null || serviceController?.Status == ServiceControllerStatus.Paused || serviceController?.Status == ServiceControllerStatus.PausePending ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Delete => serviceController == null || Win32.ServiceEx.IsPendingDeleteOrDeleted(serviceController.ServiceName) ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, _ => throw new ArgumentOutOfRangeException("Argument out of Range", new ArgumentOutOfRangeException()) }; } private readonly string[] RegexNoKill = { "DcomLaunch" }; public void RunTask(bool logExceptions = true) { if (Operation == ServiceOperation.Change && !Startup.HasValue) throw new ArgumentException("Startup property must be specified with the change operation."); if (Operation == ServiceOperation.Change && (Startup.Value > 4 || Startup.Value < 0)) throw new ArgumentException("Startup property must be between 1 and 4."); if (Operation == ServiceOperation.Change) { var action = new RegistryValueAction() { KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}", Value = "Start", Data = Startup.Value, Type = RegistryValueType.REG_DWORD, Operation = RegistryValueOperation.Set }; action.RunTask(); InProgress = false; return; } ServiceController? service; if (Device) service = GetDevice(); else service = GetService(); if (service == null) { if (Operation == ServiceOperation.Start) throw new ArgumentException("Service " + ServiceName + " not found."); return; } InProgress = true; if ((Operation == ServiceOperation.Delete && DeleteStop) || Operation == ServiceOperation.Stop) { try { foreach (ServiceController dependentService in service.DependentServices.Where(x => x.Status != ServiceControllerStatus.Stopped)) { if (RegexNoKill.Any(regex => Regex.Match(dependentService.ServiceName, regex, RegexOptions.IgnoreCase).Success)) continue; if (dependentService.Status != ServiceControllerStatus.StopPending && dependentService.Status != ServiceControllerStatus.Stopped) { try { dependentService.Stop(); } catch (Exception e) { dependentService.Refresh(); if (dependentService.Status != ServiceControllerStatus.Stopped && dependentService.Status != ServiceControllerStatus.StopPending && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Dependent service stop failed.", ("Service", ServiceName), ("Dependent Service", dependentService.ServiceName)); } } try { dependentService.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { dependentService.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && logExceptions) Log.EnqueueSafe(LogType.Warning, "Dependent service stop timeout exceeded.", null, ("Service", ServiceName), ("Dependent Service", dependentService.ServiceName)); } try { var killServ = new TaskKillAction() { ProcessID = Win32.ServiceEx.GetServiceProcessId(dependentService.ServiceName) }; killServ.RunTask(logExceptions); } catch (Exception e) { dependentService.Refresh(); if (dependentService.Status != ServiceControllerStatus.Stopped && logExceptions) Log.EnqueueSafe(LogType.Warning, "Could not kill dependent service.", null, ("Service", ServiceName), ("Dependent Service", dependentService.ServiceName)); } } } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Unexpected error while killing dependent services.", ("Service", ServiceName)); } } if (Operation == ServiceOperation.Delete) { if (DeleteStop && service.Status != ServiceControllerStatus.StopPending && service.Status != ServiceControllerStatus.Stopped) { if (RegexNoKill.Any(regex => Regex.Match(ServiceName, regex, RegexOptions.IgnoreCase).Success)) { if (logExceptions) Log.EnqueueSafe(LogType.Warning, "Skipped stopping critical service.", null, ("Service", ServiceName)); } else { try { service.Stop(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service stop failed.", ("Service", ServiceName)); } try { service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service stop timeout exceeded.", ("Service", ServiceName)); } try { var killServ = new TaskKillAction() { ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName) }; killServ.RunTask(logExceptions); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Could not kill service.", ("Service", ServiceName)); } } } if (RegistryDelete) { var action = new RegistryKeyAction() { KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}", Operation = RegistryKeyOperation.Delete }; action.RunTask(); } else { try { ServiceInstaller ServiceInstallerObj = new ServiceInstaller(); ServiceInstallerObj.Context = new InstallContext(); ServiceInstallerObj.ServiceName = service.ServiceName; ServiceInstallerObj.Uninstall(null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service uninstall failed.", ("Service", ServiceName)); } } } else if (Operation == ServiceOperation.Start) { try { service.Start(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service start failed.", ("Service", ServiceName)); } try { service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service start timeout exceeded.", ("Service", ServiceName)); } } else if (Operation == ServiceOperation.Stop) { if (RegexNoKill.Any(regex => Regex.Match(ServiceName, regex, RegexOptions.IgnoreCase).Success)) { Log.EnqueueSafe(LogType.Warning, "Skipped stopping critical service.", null, ("Service", ServiceName)); return; } try { service.Stop(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service stop failed.", ("Service", ServiceName)); } try { service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service stop timeout exceeded.", ("Service", ServiceName)); } try { var killServ = new TaskKillAction() { ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName) }; killServ.RunTask(logExceptions); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Could not kill dependent service.", ("Service", ServiceName)); } } else if (Operation == ServiceOperation.Pause) { try { service.Pause(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Paused && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service pause failed.", ("Service", ServiceName)); } try { service.WaitForStatus(ServiceControllerStatus.Paused, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Paused && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service pause timeout exceeded.", ("Service", ServiceName)); } } else if (Operation == ServiceOperation.Continue) { try { service.Pause(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service continue failed.", ("Service", ServiceName)); } try { service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Service continue timeout exceeded.", ("Service", ServiceName)); } } service?.Dispose(); Thread.Sleep(100); InProgress = false; return; } } } ================================================ FILE: Core/Actions/TaskKillAction.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Management; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using YamlDotNet.Serialization; using Core; namespace Core.Actions { class TaskKillAction : ICoreAction { [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, int dwProcessId); public enum ProcessAccessFlags : uint { QueryLimitedInformation = 0x1000 } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); [YamlMember(typeof(string), Alias = "name")] public string? ProcessName { get; set; } [YamlMember(typeof(string), Alias = "pathContains")] public string? PathContains { get; set; } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 2; public int GetProgressWeight() => ProgressWeight; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public int? ProcessID { get; set; } public string ErrorString() { string text = $"TaskKillAction failed to kill processes matching '{ProcessName}'."; try { var processes = GetProcess().Select(process => process.ProcessName).Distinct().ToList(); if (processes.Count > 1) { text = $"TaskKillAction failed to kill processes:"; foreach (var process in processes) { text += "|NEWLINE|" + process; } } else if (processes.Count == 1) text = $"TaskKillAction failed to kill process {processes[0]}."; } catch (Exception) { } return text; } public UninstallTaskStatus GetStatus() { if (InProgress) { return UninstallTaskStatus.InProgress; } List processToTerminate = new List(); if (ProcessID.HasValue) { try { processToTerminate.Add(Process.GetProcessById((int)ProcessID)); } catch (Exception) { } } else { processToTerminate = GetProcess().ToList(); } return processToTerminate.Any() ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed; } private List GetProcess() { if (ProcessID.HasValue) { var list = new List(); try { var process = Process.GetProcessById(ProcessID.Value); if (ProcessName == null || process.ProcessName.Equals(ProcessName, StringComparison.OrdinalIgnoreCase)) list.Add(process); else return list; } catch (Exception e) { return list; } } if (ProcessName == null) { return new List(); } if (ProcessName.EndsWith("*") && ProcessName.StartsWith("*")) return Process.GetProcesses().ToList() .Where(process => process.ProcessName.IndexOf(ProcessName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0).ToList(); if (ProcessName.EndsWith("*")) return Process.GetProcesses() .Where(process => process.ProcessName.StartsWith(ProcessName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)).ToList(); if (ProcessName.StartsWith("*")) return Process.GetProcesses() .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)).ToList(); return Process.GetProcessesByName(ProcessName).ToList(); } [DllImport("kernel32.dll", SetLastError=true)] static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical); private readonly string[] RegexNoKill = { "lsass", "csrss", "winlogon", "TrustedUninstaller\\.CLI", "dwm", "conhost", "ame.?wizard", "ame.?assassin" }; // These processes give access denied errors when getting their handle for IsProcessCritical. // TODO: Investigate how to properly acquire permissions. private readonly string[] RegexNotCritical = { "SecurityHealthService", "wscsvc", "MsMpEng", "SgrmBroker" }; public void RunTask(bool logExceptions = true) { InProgress = true; if (string.IsNullOrEmpty(ProcessName) && ProcessID.HasValue) { } else { if (ProcessName != null && RegexNoKill.Any(regex => Regex.Match(ProcessName, regex, RegexOptions.IgnoreCase).Success)) { return; } } if (ProcessName != null) { //If the service is svchost, we stop the service instead of killing it. if (ProcessName.Equals("svchost", StringComparison.OrdinalIgnoreCase)) { try { if (ProcessID.HasValue) { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(ProcessID.Value)) { try { var stopServ = new ServiceAction() { ServiceName = serviceName, Operation = ServiceOperation.Stop }; stopServ.RunTask(); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, "Could not kill service " + serviceName); } } } else { foreach (var process in GetProcess()) { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(process.Id)) { try { var stopServ = new ServiceAction() { ServiceName = serviceName, Operation = ServiceOperation.Stop }; stopServ.RunTask(); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, "Could not kill service " + serviceName); } } } } } catch (NullReferenceException e) { if (logExceptions) Log.EnqueueExceptionSafe(e, $"Could not find service with PID {ProcessID.Value}."); } int i; for (i = 0; i <= 6 && GetProcess().Any(); i++) { Thread.Sleep(100 * i); } if (i < 6) { InProgress = false; return; } } if (PathContains != null && !ProcessID.HasValue) { var processes = GetProcess(); foreach (var process in processes.Where(x => { try { return x.MainModule.FileName.Contains(PathContains); } catch (Exception e) { return false; } })) { if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); IsProcessCritical(hprocess, ref isCritical); CloseHandle(hprocess); if (isCritical) { continue; } } try { if (!TerminateProcess(process.Handle, 1) && logExceptions) Log.EnqueueExceptionSafe(new Win32Exception(), $"Could not find service with PID {ProcessID.Value}."); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e); } try { process.WaitForExit(1000); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e); } if (process.ProcessName == "explorer") continue; int i = 0; while (i <= 3 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { Wrap.ExecuteSafe(() => TerminateProcess(process.Handle, 1)); process.WaitForExit(500); Thread.Sleep(100); i++; } if (i >= 3 && logExceptions) Log.EnqueueSafe(LogType.Error, "Task kill timeout exceeded.", new SerializableTrace()); } InProgress = false; return; } } if (ProcessID.HasValue) { var process = Process.GetProcessById(ProcessID.Value); if (ProcessName != null && ProcessName.Equals("explorer", StringComparison.OrdinalIgnoreCase)) { try { if (!TerminateProcess(process.Handle, 1) && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, new Win32Exception(), "TerminateProcess failed with error code.", ("Process", ProcessName)); try { process.WaitForExit(1000); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Error waiting for process exit.", ("Process", ProcessName)); } } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Could not open process handle.", ("Process", ProcessName)); } } else { if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; try { IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); IsProcessCritical(hprocess, ref isCritical); CloseHandle(hprocess); } catch (InvalidOperationException e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Could not check if process is critical.", ("Process", ProcessName)); return; } if (isCritical) { Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); return; } } try { if (!TerminateProcess(process.Handle, 1) && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, new Win32Exception(), "TerminateProcess failed with error code.", ("Process", ProcessName)); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Could not open process handle.", ("Process", ProcessName)); } try { process.WaitForExit(1000); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Error waiting for process exit.", ("Process", ProcessName)); } } int i = 0; while (i <= 3 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { try { try { TerminateProcess(process.Handle, 1); } catch (Exception e) { } process.WaitForExit(500); } catch (Exception e) { } Thread.Sleep(100); i++; } if (i >= 3 && logExceptions) Log.EnqueueSafe(LogType.Warning, "Task kill timeout exceeded.", new SerializableTrace(), ("Process", ProcessName)); } else { var processes = GetProcess(); foreach (var process in processes) { if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; try { IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); IsProcessCritical(hprocess, ref isCritical); CloseHandle(hprocess); } catch (InvalidOperationException e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Could not check if process is critical.", ("Process", ProcessName)); continue; } if (isCritical) { continue; } } try { if (!TerminateProcess(process.Handle, 1) && logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, new Win32Exception(), "TerminateProcess failed with error code.", ("Process", ProcessName)); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Could not open process handle.", ("Process", ProcessName)); } try { process.WaitForExit(1000); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(LogType.Warning, e, "Error waiting for process exit.", ("Process", ProcessName)); } if (process.ProcessName == "explorer") continue; int i = 0; while (i <= 3 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { try { try { TerminateProcess(process.Handle, 1); } catch (Exception e) { } process.WaitForExit(500); } catch (Exception e) { } Thread.Sleep(100); i++; } if (i >= 3 && logExceptions) Log.EnqueueSafe(LogType.Warning, "Task kill timeout exceeded.", new SerializableTrace(), ("Process", ProcessName)); } } InProgress = false; return; } } } ================================================ FILE: Core/Actions/UninstallTaskStatus.cs ================================================ namespace Core.Actions { public enum UninstallTaskStatus { Completed, InProgress, ToDo } } ================================================ FILE: Core/Core.projitems ================================================  $(MSBuildAllProjects);$(MSBuildThisFileFullPath) true EED9CB45-0811-459B-B543-34865080843E Core Always ================================================ FILE: Core/Core.shproj ================================================ {DEBB633C-F189-49AE-92DD-4EB71CECCF90} v4.7.2 ================================================ FILE: Core/Core.shproj.DotSettings ================================================  True True True True True ================================================ FILE: Core/Exceptions/UnexpectedException.cs ================================================ using System; namespace Core.Exceptions { public class UnexpectedException : Exception { public UnexpectedException() : base() {} public UnexpectedException(string message) : base(message) {} public UnexpectedException(string message, Exception innerException) : base(message, innerException) {} } } ================================================ FILE: Core/Helper/Interop/Helper.cs ================================================ using System; using System.Globalization; using System.Runtime.InteropServices; namespace Core { public static class Helper { [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] public static extern string FormatVolume(string letter, string formatType, uint allocationSize, string volumeLabel); [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] public static extern string DeletePartitions(uint driveIndex); /// /// Only supports REG_SZ, REG_MULTI_SZ (I think), DWORD, and QWORD value types. /// [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] public static extern string GetValue(IntPtr data, string key, string valueName); /// /// Gets all REG_SZ, REG_MULTI_SZ (I think), DWORD, and QWORD value types. /// The values are delimited by '\n', and the value name is separated from /// the value with the following 3 wide string: ":|:" /// [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] public static extern string GetValues(IntPtr data, string key); public static ulong GetDWordValue(IntPtr data, string key, string valueName) => uint.Parse(GetValue(data, key, valueName), NumberStyles.HexNumber); public static ulong GetQWordValue(IntPtr data, string key, string valueName) => ulong.Parse(GetValue(data, key, valueName), NumberStyles.HexNumber); } } ================================================ FILE: Core/Helper/client-helper.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.6.33815.320 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "client-helper", "client-helper.vcxproj", "{05923F4D-3076-42D0-A9B1-048AD50DF078}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {05923F4D-3076-42D0-A9B1-048AD50DF078}.Debug|x64.ActiveCfg = Release|x64 {05923F4D-3076-42D0-A9B1-048AD50DF078}.Debug|x64.Build.0 = Release|x64 {05923F4D-3076-42D0-A9B1-048AD50DF078}.Debug|x86.ActiveCfg = Debug|Win32 {05923F4D-3076-42D0-A9B1-048AD50DF078}.Debug|x86.Build.0 = Debug|Win32 {05923F4D-3076-42D0-A9B1-048AD50DF078}.Release|x64.ActiveCfg = Release|x64 {05923F4D-3076-42D0-A9B1-048AD50DF078}.Release|x64.Build.0 = Release|x64 {05923F4D-3076-42D0-A9B1-048AD50DF078}.Release|x86.ActiveCfg = Release|Win32 {05923F4D-3076-42D0-A9B1-048AD50DF078}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {35C6C359-0DA1-42E3-B516-582ADACF61E1} EndGlobalSection EndGlobal ================================================ FILE: Core/Helper/client-helper.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 16.0 Win32Proj {05923f4d-3076-42d0-a9b1-048ad50df078} clienthelper 10.0 DynamicLibrary true v143 Unicode DynamicLibrary false v143 true Unicode DynamicLibrary true v143 Unicode DynamicLibrary false v143 true MultiByte Level3 true WIN32;_DEBUG;CLIENTHELPER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h Windows true false Level3 true true true WIN32;NDEBUG;CLIENTHELPER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h Windows true true true false Level3 true _DEBUG;CLIENTHELPER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h Windows true false Level3 true true true NDEBUG;CLIENTHELPER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h Default Windows true true true false Create Create Create Create ================================================ FILE: Core/Helper/client-helper.vcxproj.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Header Files Header Files Source Files Source Files ================================================ FILE: Core/Helper/dllmain.cpp ================================================ // dllmain.cpp : Defines the entry point for the DLL application. #include "pch.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } ================================================ FILE: Core/Helper/framework.h ================================================ #pragma once #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files #include ================================================ FILE: Core/Helper/pch.cpp ================================================ #define _CRT_SECURE_NO_WARNINGS #include "pch.h" struct offsets { long block_size; char block_type[2]; // "lf" "il" "ri" short count; long first; long hash; }; struct key_block { long block_size; char block_type[2]; // "nk" char dummya[18]; int subkey_count; char dummyb[4]; int subkeys; char dummyc[4]; int value_count; int offsets; char dummyd[28]; short len; short du; char name; }; struct value_block { long block_size; char block_type[2]; // "vk" short name_len; long size; long offset; long value_type; short flags; short dummy; char name; }; LPWSTR walk(char* path, key_block* key, char* targetKey, int targetKeyLen, char* targetValue, bool firstLoop) { static char* root, * full; if (firstLoop) { root = (char*)key - 0x20; full = path; } // add current key name to printed path memcpy(path++, "\\", 2); memcpy(path, &key->name, key->len); path += key->len; *path = 0; int pathLen = strlen(full); if (pathLen >= 6 && (pathLen - 6 > targetKeyLen || !(_strnicmp(full + 6, targetKey, pathLen - 6) == 0))) return NULL; if (pathLen < 6 || pathLen - 6 < targetKeyLen || !(_strnicmp(full + 6, targetKey, targetKeyLen) == 0)) { // for simplicity we can imagine keys as directories in filesystem and values // as files. // and since we already dumped values for this dir we will now iterate // thru subdirectories in the same way offsets* item = (offsets*)(root + key->subkeys); for (int i = 0;i < item->count;i++) { // in case of too many subkeys this list contain just other lists offsets* subitem = (offsets*)((&item->first)[i] + root); // usual directory traversal if (item->block_type[1] == 'f' || item->block_type[1] == 'h') { // for now we skip hash codes (used by regedit for faster search) LPWSTR result = walk(path, (key_block*)((&item->first)[i * 2] + root), targetKey, targetKeyLen, targetValue, false); if (result != NULL) return result; } else for (int j = 0;j < subitem->count;j++) { // also ms had chosen to skip hashes altogether in this case LPWSTR result = walk(path, (key_block*)((&subitem->first)[item->block_type[1] == 'i' ? j * 2 : j] + root), targetKey, targetKeyLen, targetValue, false); if (result != NULL) return result; } } return NULL; } long totalSize = 0; for (int o = 0; targetValue == NULL && o < key->value_count;o++) { value_block* val = (value_block*)(((int*)(key->offsets + root + 4))[o] + root); int length = val->size & 0xffff; totalSize += length; } // print all contained values wchar_t* unicodeResult = targetValue == NULL ? new wchar_t[totalSize] {0} : NULL; for (int o = 0;o < key->value_count;o++) { value_block* val = (value_block*)(((int*)(key->offsets + root + 4))[o] + root); // we skip nodes without values if (!val->offset) continue; // data are usually in separate blocks without types char* data = root + val->offset + 4; // but for small values MS added optimization where // if bit 31 is set data are contained wihin the key itself to save space if (val->size & 1 << 31) { data = (char*)&val->offset; } // notice that we use memcopy for key/value names everywhere instead of strcat // reason is that malware/wiruses often write non nulterminated strings to // hide from win32 api //*path = '\\'; if (!val->name_len) *path = ' '; //memcpy(path + 1, &val->name, val->name_len); path[val->name_len + 1] = 0; //printf("%s [%d] = ", full, val->value_type); int length = val->size & 0xffff; //printf("%i\n", val->value_type); if (val->value_type != 1 && val->value_type != 7 && val->value_type != 4 && val->value_type != 11) continue; if (targetValue == NULL) { // print types 1 and 7 as unicode strings if (val->value_type == 1 || val->value_type == 7) { char* ansiResult = new char[length + 1] {0}; for (int i = 0; i < length; i++) { char concat[1]; sprintf(concat, "%c", data[i]); strcat(ansiResult, concat); } size_t size = strlen(ansiResult) + 1; wchar_t* unicodePart = new wchar_t[size]; size_t outSize; mbstowcs_s(&outSize, unicodePart, size, ansiResult, size - 1); char* name = new char[val->name_len + 1] {0}; memcpy(name, &val->name, val->name_len); name[val->name_len] = 0; size_t nameSize = strlen(name) + 1; wchar_t* unicodeName = new wchar_t[nameSize] {0}; mbstowcs_s(&outSize, unicodeName, nameSize, name, nameSize - 1); if (o != 0) wcscat(unicodeResult, L"\n"); wcscat(unicodeResult, unicodeName); wcscat(unicodeResult, L":|:"); wcscat(unicodeResult, unicodePart); delete[] ansiResult; delete[] unicodePart; delete[] name; delete[] unicodeName; } else { char* ansiResult = new char[(length * 2) + 1] {0}; unsigned int bits[4]; unsigned int longBits[8]; bool skip = false; int i; for (i = 0; i < length; i++) { if (length == 4) { bits[i] = data[i]; continue; } if (length == 8) { longBits[i] = data[i]; continue; } skip = true; delete[] ansiResult; break; char concat[12]; sprintf(concat, "%02X", data[i]); strcat(ansiResult, concat); } if (skip) continue; if (length == 4) { unsigned int result = (bits[0] << 24) | (bits[1] << 16) | (bits[2] << 8) | bits[3]; unsigned int resultat = 0; char* source, * destination; int i; source = (char*)&result; destination = ((char*)&resultat) + sizeof(unsigned int); for (i = 0; i < sizeof(unsigned int); i++) *(--destination) = *(source++); sprintf(ansiResult, "%X", resultat); } else if (length == 8) { unsigned int shortPart = (longBits[0] << 24) | (longBits[1] << 16) | (longBits[2] << 8) | longBits[3]; unsigned int longPart = (longBits[4] << 24) | (longBits[5] << 16) | (longBits[6] << 8) | longBits[7]; unsigned long long result = (long long)shortPart << 32 | longPart; unsigned long long resultat = 0; char* source, * destination; int i; source = (char*)&result; destination = ((char*)&resultat) + sizeof(unsigned long long); for (i = 0; i < sizeof(unsigned long long); i++) *(--destination) = *(source++); sprintf(ansiResult, "%I64X", resultat); } size_t size = strlen(ansiResult) + 1; wchar_t* unicodePart = new wchar_t[size]; size_t outSize; mbstowcs_s(&outSize, unicodePart, size, ansiResult, size - 1); char* name = new char[val->name_len + 1] {0}; memcpy(name, &val->name, val->name_len); name[val->name_len] = 0; size_t nameSize = strlen(name) + 1; wchar_t* unicodeName = new wchar_t[nameSize] {0}; mbstowcs_s(&outSize, unicodeName, nameSize, name, nameSize - 1); if (o != 0) wcscat(unicodeResult, L"\n"); wcscat(unicodeResult, unicodeName); wcscat(unicodeResult, L":|:"); wcscat(unicodeResult, unicodePart); delete[] ansiResult; delete[] unicodePart; delete[] name; delete[] unicodeName; } } else if (_strnicmp(targetValue, &val->name, val->name_len) == 0) { // print types 1 and 7 as unicode strings if (val->value_type == 1 || val->value_type == 7) { char* ansiResult = new char[length + 1] {0}; for (int i = 0; i < length; i++) { char concat[1]; sprintf(concat, "%c", data[i]); strcat(ansiResult, concat); } size_t size = strlen(ansiResult) + 1; unicodeResult = new wchar_t[size] {0}; size_t outSize; mbstowcs_s(&outSize, unicodeResult, size, ansiResult, size - 1); delete[] ansiResult; return unicodeResult; } else { char* ansiResult = new char[(length * 2) + 1] {0}; unsigned int bits[4]; unsigned int longBits[8]; int i; for (i = 0; i < length; i++) { if (length == 4) { bits[i] = data[i]; continue; } if (length == 8) { longBits[i] = data[i]; continue; } delete[] ansiResult; return NULL; } if (length == 4) { unsigned int result = (bits[0] << 24) | (bits[1] << 16) | (bits[2] << 8) | bits[3]; unsigned int resultat = 0; char* source, * destination; int i; source = (char*)&result; destination = ((char*)&resultat) + sizeof(unsigned int); for (i = 0; i < sizeof(unsigned int); i++) *(--destination) = *(source++); sprintf(ansiResult, "%X", resultat); } else if (length == 8) { unsigned int shortPart = (longBits[0] << 24) | (longBits[1] << 16) | (longBits[2] << 8) | longBits[3]; unsigned int longPart = (longBits[4] << 24) | (longBits[5] << 16) | (longBits[6] << 8) | longBits[7]; unsigned long long result = (long long)shortPart << 32 | longPart; unsigned long long resultat = 0; char* source, * destination; int i; source = (char*)&result; destination = ((char*)&resultat) + sizeof(unsigned long long); for (i = 0; i < sizeof(unsigned long long); i++) *(--destination) = *(source++); sprintf(ansiResult, "%I64X", resultat); } size_t size = strlen(ansiResult) + 1; unicodeResult = new wchar_t[size] {0}; size_t outSize; mbstowcs_s(&outSize, unicodeResult, size, ansiResult, size - 1); delete[] ansiResult; return unicodeResult; } } } return unicodeResult; } #pragma comment(lib, "rpcrt4.lib") #define SAFE_RELEASE(x) if (x) { x->Release(); x = NULL; } #define SAFE_FREE(x) if (x) { CoTaskMemFree(x); } BSTR GetVdsDiskInterface( DWORD driveIndex, const IID InterfaceIID, void** pInterfaceInstance ) { wchar_t result[256] = L"Unknown error."; HRESULT hr; IVdsServiceLoader* pLoader; IVdsService* pService; IUnknown* pUnk; IVdsAsync* pAsync; IEnumVdsObject* pEnum; ULONG ulFetched; wchar_t physicalName[24]; swprintf(physicalName, ARRAYSIZE(physicalName), L"\\\\?\\PhysicalDrive%lu", driveIndex); // Create a loader instance hr = CoCreateInstance(CLSID_VdsLoader, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, IID_IVdsServiceLoader, (void**)&pLoader ); wcsncpy(result, L"CoCreateInstance failed.", 256); if (SUCCEEDED(hr)) { // Load the service on the machine. hr = pLoader->LoadService(NULL, &pService); SAFE_RELEASE(pLoader); pLoader = NULL; wcsncpy(result, L"LoadService failed.", 256); if (SUCCEEDED(hr)) { pService->WaitForServiceReady(); hr = pService->QueryProviders(VDS_QUERY_SOFTWARE_PROVIDERS, &pEnum); pService->Release(); if (SUCCEEDED(hr)) { while (pEnum->Next(1, &pUnk, &ulFetched) == S_OK) { IVdsProvider* pProvider; IVdsSwProvider* pSwProvider; IEnumVdsObject* pEnumPack; IUnknown* pPackUnk; hr = pUnk->QueryInterface(IID_IVdsProvider, (void**)&pProvider); pUnk->Release(); if (SUCCEEDED(hr)) { hr = pProvider->QueryInterface(IID_IVdsSwProvider, (void**)&pSwProvider); pProvider->Release(); if (SUCCEEDED(hr)) { hr = pSwProvider->QueryPacks(&pEnumPack); pSwProvider->Release(); if (SUCCEEDED(hr)) { while (pEnumPack->Next(1, &pPackUnk, &ulFetched) == S_OK) { IVdsPack* pPack; IEnumVdsObject* pEnumDisk; IUnknown* pDiskUnk; hr = pPackUnk->QueryInterface(IID_IVdsPack, (void**)&pPack); pPackUnk->Release(); if (SUCCEEDED(hr)) { hr = pPack->QueryDisks(&pEnumDisk); pPack->Release(); if (SUCCEEDED(hr)) { while (pEnumDisk->Next(1, &pDiskUnk, &ulFetched) == S_OK) { VDS_DISK_PROP prop; IVdsDisk* pDisk; hr = pDiskUnk->QueryInterface(IID_IVdsDisk, (void**)&pDisk); pDiskUnk->Release(); if (SUCCEEDED(hr)) { hr = pDisk->GetProperties(&prop); if ((hr != S_OK) && (hr != VDS_S_PROPERTIES_INCOMPLETE)) { pDisk->Release(); continue; } hr = (HRESULT)_wcsicmp(physicalName, prop.pwszName); CoTaskMemFree(prop.pwszName); if (hr == S_OK) { hr = pDisk->QueryInterface(InterfaceIID, pInterfaceInstance); pDisk->Release(); if (SUCCEEDED(hr)) { pEnumDisk->Release(); pEnumPack->Release(); pEnum->Release(); return SysAllocString(L"Success"); } } pDisk->Release(); } } pEnumDisk->Release(); } } } pEnumPack->Release(); } } } } pEnum->Release(); } } } return result; } BSTR RefreshVds( ) { wchar_t result[256] = L"Unknown error."; HRESULT hr = S_FALSE; IVdsServiceLoader* pLoader; IVdsService* pService; hr = CoInitialize(NULL); // Will fail when called from C# since thread was already initialized from there if (true) { // Create a loader instance hr = CoCreateInstance(CLSID_VdsLoader, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, IID_IVdsServiceLoader, (void**)&pLoader ); wcsncpy(result, L"CoCreateInstance failed.", 256); if (SUCCEEDED(hr)) { // Load the service on the machine. hr = pLoader->LoadService(NULL, &pService); SAFE_RELEASE(pLoader); wcsncpy(result, L"LoadService failed.", 256); if (SUCCEEDED(hr)) { pService->WaitForServiceReady(); HRESULT hr1 = pService->Refresh(); HRESULT hr2 = pService->Reenumerate(); Sleep(1500); pService->Release(); if (SUCCEEDED(hr1) && SUCCEEDED(hr2)) return SysAllocString(L"Success"); if (!SUCCEEDED(hr1) && !SUCCEEDED(hr2)) return SysAllocString(L"Failed to refresh and renumerate disks."); if (!SUCCEEDED(hr1)) return SysAllocString(L"Failed to refresh disks."); if (!SUCCEEDED(hr2)) return SysAllocString(L"Failed to reenumerate disks."); } } } return SysAllocString(result); } BSTR CombineBSTRs( BSTR bstr1, BSTR bstr2, BOOL freeInput ) { _bstr_t wrap1 = bstr1; _bstr_t wrap2 = bstr2; _bstr_t result = wrap1 + wrap2; if (freeInput) { SysFreeString(bstr1); SysFreeString(bstr2); } return result; } extern "C" { __declspec(dllexport) BSTR DeletePartitions( DWORD driveIndex ) { //wchar_t result[256] = L"Unknown error."; HRESULT hr = S_FALSE; VDS_PARTITION_PROP* prop_array = NULL; LONG i, prop_array_size; IVdsAdvancedDisk* pAdvancedDisk = NULL; BSTR getResult = GetVdsDiskInterface(driveIndex, IID_IVdsAdvancedDisk, (void**)&pAdvancedDisk); if (wcscmp(getResult, L"Success") != 0) return CombineBSTRs(SysAllocString(L"Could not get VDS disk interface: "), getResult, true); if (pAdvancedDisk == NULL) { BSTR refreshResult = RefreshVds(); SysFreeString(refreshResult); SysFreeString(getResult); getResult = GetVdsDiskInterface(driveIndex, IID_IVdsAdvancedDisk, (void**)&pAdvancedDisk); if (wcscmp(getResult, L"Success") != 0 || pAdvancedDisk == NULL) { return CombineBSTRs(SysAllocString(L"Could not get VDS disk interface after refresh: "), getResult, true); } } char erroredPartitions[128]; BOOLEAN error = false; BSTR errorString = NULL; hr = pAdvancedDisk->QueryPartitions(&prop_array, &prop_array_size); if (SUCCEEDED(hr)) { for (i = 0; i < prop_array_size; i++) { if (!SUCCEEDED(pAdvancedDisk->DeletePartition(prop_array[i].ullOffset, TRUE, TRUE))) { if (!error) errorString = SysAllocString(L"Could not remove partitions: "); wchar_t partitionSize[256]; _swprintf(partitionSize, L"\n%d - %llu MB", i, (prop_array[i].ullSize / 1024) / 1024); errorString = CombineBSTRs(errorString, SysAllocString(partitionSize), true); error = true; } } } CoTaskMemFree(prop_array); pAdvancedDisk->Release(); return error ? errorString : SysAllocString(L"Success"); } __declspec(dllexport) BSTR FormatVolume( LPCWSTR letter, LPWSTR pwszFileSystemTypeName, UINT32 ulDesiredUnitAllocationSize, LPWSTR pwszLabel) { wchar_t result[256] = L"Unknown error."; HRESULT hr; HRESULT hrAsync; IVdsServiceLoader* pLoader; IVdsService* pService; IUnknown* pUnk; IVdsVolume* pVolume; IVdsVolumeMF3* pVolumeMF3; IVdsAsync* pAsync; VDS_ASYNC_OUTPUT AsyncOut; if (letter == NULL || pwszFileSystemTypeName == NULL) { return ::SysAllocString(L"Invalid parameters."); } // Convert drive letter from widechar char driveLetter; int bytesConverted; int res = wctomb_s(&bytesConverted, &driveLetter, sizeof(driveLetter), letter[0]); if (res != 0) return ::SysAllocString(L"Error converting letter."); hr = CoInitialize(NULL); // Will fail when called from C# since thread was already initialized from there if (true) { // Create a loader instance hr = CoCreateInstance(CLSID_VdsLoader, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, IID_IVdsServiceLoader, (void**)&pLoader ); wcsncpy(result, L"CoCreateInstance failed.", 256); if (SUCCEEDED(hr)) { // Load the service on the machine. hr = pLoader->LoadService(NULL, &pService); SAFE_RELEASE(pLoader); pLoader = NULL; wcsncpy(result, L"LoadService failed.", 256); if (SUCCEEDED(hr)) { pService->WaitForServiceReady(); // Access to volume interface via drive letter VDS_DRIVE_LETTER_PROP mDriveLetterPropArray[1]; hr = pService->QueryDriveLetters(driveLetter, 1, mDriveLetterPropArray); wcsncpy(result, L"QueryDriveLetters access failed.", 256); if (SUCCEEDED(hr)) { hr = pService->GetObject(mDriveLetterPropArray->volumeId, VDS_OT_VOLUME, &pUnk); /* WCHAR str[256]; StringFromGUID2(mDriveLetterPropArray->volumeId, str, _countof(str)); wprintf(str); printf("\r\n%lu\r\n", hr); printf("%lu\r\n", GetLastError()); */ wcsncpy(result, L"UuidFromString failed.", 256); if (SUCCEEDED(hr)) { hr = pUnk->QueryInterface(IID_IVdsVolume, (void**)&pVolume); wcsncpy(result, L"QueryInterface failed.", 256); if (SUCCEEDED(hr)) { // Access volume format interface hr = pVolume->QueryInterface(IID_IVdsVolumeMF3, (void**)&pVolumeMF3); wcsncpy(result, L"QueryInterface MF3 failed.", 256); if (SUCCEEDED(hr)) { // Execute format operation hr = pVolumeMF3->FormatEx2( pwszFileSystemTypeName, 1, ulDesiredUnitAllocationSize, pwszLabel, VDS_FSOF_QUICK | VDS_FSOF_FORCE, &pAsync); hr = pAsync->Wait(&hrAsync, &AsyncOut); #pragma region Error handling if (FAILED(hr)) { wcsncpy(result, L"Failed to wait for asynchronous volume format completion.", 256); } else if (FAILED(hrAsync)) { switch (hrAsync) { case VDS_E_NOT_SUPPORTED: wcsncpy(result, L"The operation is not supported by the object.", 256); break; case VDS_E_ACCESS_DENIED: wcsncpy(result, L"Access denied.", 256); break; case VDS_E_ACTIVE_PARTITION: break; default: wcsncpy(result, L"Error occurred in FormatEx2.", 256); break; } } else if (SUCCEEDED(hr)) { wcsncpy(result, L"Success", 256); } #pragma endregion SAFE_RELEASE(pVolumeMF3); } SAFE_RELEASE(pVolume); } SAFE_RELEASE(pUnk); } } SAFE_RELEASE(pService); } } CoUninitialize(); } return SysAllocString(result); } __declspec(dllexport) BSTR GetValue( char* data, LPWSTR key, LPWSTR valueName) { char keyBuffer[256]; wcstombs(keyBuffer, key, 256); char valueBuffer[128]; wcstombs(valueBuffer, valueName, 128); /* char memBuffer[30000]; memcpy(memBuffer, data, sizeof(char) * 10000); memBuffer[sizeof(char) * 10000 - 1] = '\0'; printf("\n"); fwrite(memBuffer, sizeof(char), 10000, stdout); printf("\n"); */ char path[0x1000] = { 0 }; // we just skip 1k header and start walking root key tree LPWSTR result = walk(path, (key_block*)(data + 0x1020), keyBuffer, strlen(keyBuffer), valueBuffer, true); if (result == NULL) { wcsncpy(result, L"NOT FOUND", 256); } BSTR returnValue = ::SysAllocString(result); delete[] result; return returnValue; } __declspec(dllexport) BSTR GetValues( char* data, LPWSTR key) { char keyBuffer[256]; wcstombs(keyBuffer, key, 256); /* char memBuffer[30000]; memcpy(memBuffer, data, sizeof(char) * 10000); memBuffer[sizeof(char) * 10000 - 1] = '\0'; printf("\n"); fwrite(memBuffer, sizeof(char), 10000, stdout); printf("\n"); */ char path[0x1000] = { 0 }; // we just skip 1k header and start walking root key tree LPWSTR result = walk(path, (key_block*)(data + 0x1020), keyBuffer, strlen(keyBuffer), NULL, true); if (result == NULL) { wcsncpy(result, L"NOT FOUND", 256); } BSTR returnValue = ::SysAllocString(result); delete[] result; return returnValue; } } ================================================ FILE: Core/Helper/pch.h ================================================ // pch.h: This is a precompiled header file. // Files listed below are compiled only once, improving build performance for future builds. // This also affects IntelliSense performance, including code completion and many code browsing features. // However, files listed here are ALL re-compiled if any one of them is updated between builds. // Do not add files here that you will be updating frequently as this negates the performance advantage. #ifndef PCH_H #define PCH_H // add headers that you want to pre-compile here #include "framework.h" #include #include #include #include #include #include #include #endif //PCH_H ================================================ FILE: Core/Miscellaneous/Serializables.cs ================================================ using System; using System.Runtime.Serialization; using JetBrains.Annotations; namespace Core.Miscellaneous { public static class Serializables { [Serializable] public class SerializableType : ISerializable { [NonSerialized] public readonly Type Type; public string TypeName { get; set; } public SerializableType(Type type) { Type = type; TypeName = type.AssemblyQualifiedName; } protected SerializableType(SerializationInfo info, StreamingContext context) { TypeName = info.GetString("TypeName"); Type = Type.GetType(TypeName); } public void GetObjectData(SerializationInfo info, StreamingContext context) => info.AddValue("TypeName", TypeName); } [Serializable] public class SerializableValue : ISerializable { public SerializableType Type { get; set; } [CanBeNull] public object Value { get; set; } public SerializableValue(Type type, [CanBeNull] object value) { Type = new SerializableType(type); Value = value; } public SerializableValue([NotNull] object value) { Type = new SerializableType(value.GetType()); Value = value; } protected SerializableValue(SerializationInfo info, StreamingContext context) { Type = (SerializableType)info.GetValue("Type", typeof(SerializableType)); Value = info.GetValue("Value", Type.Type); } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Type", Type); info.AddValue("Value", Value); } } } } ================================================ FILE: Core/Miscellaneous/StreamReaderWithPosition.cs ================================================ using System; using System.Diagnostics.Contracts; using System.IO; using System.Reflection; using System.Text; namespace Core.Miscellaneous { internal static class StreamReaderPosition { readonly static FieldInfo charPosField = typeof(StreamReader).GetField("charPos", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); readonly static FieldInfo byteLenField = typeof(StreamReader).GetField("byteLen", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("charBuffer", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); internal static long GetPosition(this StreamReader reader) { // shift position back from BaseStream.Position by the number of bytes read // into internal buffer. int byteLen = (int)byteLenField.GetValue(reader); var position = reader.BaseStream.Position - byteLen; // if we have consumed chars from the buffer we need to calculate how many // bytes they represent in the current encoding and add that to the position. int charPos = (int)charPosField.GetValue(reader); if (charPos > 0) { var charBuffer = (char[])charBufferField.GetValue(reader); var encoding = reader.CurrentEncoding; var bytesConsumed = encoding.GetBytes(charBuffer, 0, charPos).Length; position += bytesConsumed; } return position; } internal static void SetPosition(this StreamReader reader, long position) { reader.DiscardBufferedData(); reader.BaseStream.Seek(position, SeekOrigin.Begin); } } /// /// https://github.com/microsoft/referencesource/blob/master/mscorlib/system/io/streamreader.cs /// public class StreamReaderWithPosition : StreamReader { private readonly Stream stream; private readonly Encoding encoding; private Decoder decoder; private int charLen; private int charPos; private char[] charBuffer; private byte[] byteBuffer; // Record the number of valid bytes in the byteBuffer, for a few checks. private int byteLen; // This is used only for preamble detection private int bytePos; // Encoding's preamble, which identifies this encoding. private byte[] _preamble; // Whether we must still check for the encoding's given preamble at the // beginning of this file. private bool _checkPreamble; // Whether the stream is most likely not going to give us back as much // data as we want the next time we call it. We must do the computation // before we do any byte order mark handling and save the result. Note // that we need this to allow users to handle streams used for an // interactive protocol, where they block waiting for the remote end // to send a response, like logging in on a Unix machine. public StreamReaderWithPosition(Stream stream, Encoding encoding) : base(stream, encoding) { this.stream = stream; this.encoding = encoding; decoder = encoding.GetDecoder(); _preamble = encoding.GetPreamble(); _checkPreamble = (_preamble.Length > 0); byteBuffer = new byte[1024]; charBuffer = new char[encoding.GetMaxCharCount(1024)]; } public long Position { get { var position = stream.Position - byteLen; if (charPos > 0) { var bytesConsumed = encoding.GetBytes(charBuffer, 0, charPos).Length; position += bytesConsumed; } return position; } } public void DiscardBuffered() { byteLen = 0; charLen = 0; charPos = 0; // in general we'd like to have an invariant that encoding isn't null. However, // for startup improvements for NullStreamReader, we want to delay load encoding. if (encoding != null) { decoder = encoding.GetDecoder(); } } private void CompressBuffer(int n) { Contract.Assert(byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length. Are two threads using this StreamReader at the same time?"); Buffer.BlockCopy(byteBuffer, n, byteBuffer, 0, byteLen - n); byteLen -= n; } public void Seek(long offset) { DiscardBuffered(); this.stream.Seek(offset, SeekOrigin.Begin); } // Trims the preamble bytes from the byteBuffer. This routine can be called multiple times // and we will buffer the bytes read until the preamble is matched or we determine that // there is no match. If there is no match, every byte read previously will be available // for further consumption. If there is a match, we will compress the buffer for the // leading preamble bytes private bool IsPreamble() { if (!_checkPreamble) return _checkPreamble; Contract.Assert(bytePos <= _preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length. Are two threads using this StreamReader at the same time?"); int len = (byteLen >= (_preamble.Length)) ? (_preamble.Length - bytePos) : (byteLen - bytePos); for (int i = 0; i < len; i++, bytePos++) { if (byteBuffer[bytePos] != _preamble[bytePos]) { bytePos = 0; _checkPreamble = false; break; } } Contract.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); if (_checkPreamble) { if (bytePos == _preamble.Length) { // We have a match CompressBuffer(_preamble.Length); bytePos = 0; _checkPreamble = false; } } return _checkPreamble; } internal virtual int ReadBuffer() { charLen = 0; charPos = 0; if (!_checkPreamble) byteLen = 0; do { if (_checkPreamble) { Contract.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); int len = stream.Read(byteBuffer, bytePos, byteBuffer.Length - bytePos); Contract.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); if (len == 0) { // EOF but we might have buffered bytes from previous // attempt to detect preamble that needs to be decoded now if (byteLen > 0) { charLen += decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, charLen); // Need to zero out the byteLen after we consume these bytes so that we don't keep infinitely hitting this code path bytePos = byteLen = 0; } return charLen; } byteLen += len; } else { Contract.Assert(bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); byteLen = stream.Read(byteBuffer, 0, byteBuffer.Length); Contract.Assert(byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); if (byteLen == 0) // We're at EOF return charLen; } // Check for preamble before detect encoding. This is not to override the // user suppplied Encoding for the one we implicitly detect. The user could // customize the encoding which we will loose, such as ThrowOnError on UTF8 if (IsPreamble()) continue; charLen += decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, charLen); } while (charLen == 0); //Console.WriteLine("ReadBuffer called. chars: "+charLen); return charLen; } public new bool EndOfStream { get { if (charPos < charLen) return false; // This may block on pipes! int numRead = ReadBuffer(); return numRead == 0; } } public override int Peek() { if (charPos == charLen) { if (ReadBuffer() == 0) return -1; } return charBuffer[charPos]; } public override string ReadLine() { if (charPos == charLen) { if (ReadBuffer() == 0) return null; } StringBuilder sb = null; do { int i = charPos; do { char ch = charBuffer[i]; // Note the following common line feed chars: // \n - UNIX \r\n - DOS \r - Mac if (ch == '\r' || ch == '\n') { String s; if (sb != null) { sb.Append(charBuffer, charPos, i - charPos); s = sb.ToString(); } else { s = new String(charBuffer, charPos, i - charPos); } charPos = i + 1; if (ch == '\r' && (charPos < charLen || ReadBuffer() > 0)) { if (charBuffer[charPos] == '\n') charPos++; } return s; } i++; } while (i < charLen); i = charLen - charPos; if (sb == null) sb = new StringBuilder(i + 80); sb.Append(charBuffer, charPos, i); } while (ReadBuffer() > 0); return sb.ToString(); } } } ================================================ FILE: Core/Miscellaneous/XmlDeserializer.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; namespace Core.Miscellaneous { public abstract class XmlDeserializable : IXmlSerializable { public class XmlAllowInlineArrayItemAttribute : Attribute { } public class XmlRequiredAttribute : Attribute { public bool AllowEmptyString { get; set; } public XmlRequiredAttribute() => AllowEmptyString = true; public XmlRequiredAttribute(bool allowEmptyString) => AllowEmptyString = allowEmptyString; } [Obsolete("XmlDeserializable does not support XmlInclude.")] public class XmlIncludeAttribute : Attribute { } /// /// This method gets called upon after deserialization, but before Deserialize returns. This is a good place to throw exceptions to the Deserialize call. /// public virtual void Validate() { } public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); var classType = reader.Name; List assignedProperties = new List(); if (reader.HasAttributes) { while (reader.MoveToNextAttribute()) { var property = properties.FirstOrDefault(x => x.GetCustomAttribute() == null && (x.GetCustomAttributes().FirstOrDefault(attr => !string.IsNullOrEmpty(attr.AttributeName) && attr.AttributeName == reader.Name) != null || (x.Name == reader.Name && x.GetCustomAttribute() != null))) ?? throw new XmlException($"Unrecognized attribute '{reader.Name}'"); if (assignedProperties.Contains(property.Name)) throw new XmlException($"Duplicate assignment for property '{property.Name}'"); assignedProperties.Add(property.Name); property.SetValue(this, ReadContentValue(reader, property.PropertyType)); } } while (true) { if (!MoveToNextElement(reader, classType)) { var requiredProperty = properties.FirstOrDefault(x => x.GetCustomAttribute() == null && x.GetCustomAttribute() != null && (!assignedProperties.Contains(x.Name) || (x.PropertyType == typeof(string) && !x.GetCustomAttribute().AllowEmptyString && string.IsNullOrWhiteSpace((string)x.GetValue(this))))); if (requiredProperty != null && !assignedProperties.Contains(requiredProperty.Name)) throw new XmlException($"Required property '{requiredProperty.Name}' must be set."); else if (requiredProperty != null) throw new XmlException($"Property '{requiredProperty.Name}' must not be empty."); Validate(); return; } var property = properties.FirstOrDefault(x => x.GetCustomAttribute() == null && (x.GetCustomAttributes().FirstOrDefault(attr => !string.IsNullOrEmpty(attr.ElementName) && attr.ElementName == reader.Name) != null || x.Name == reader.Name)) ?? throw new XmlException($"Unrecognized element '{reader.Name}'"); var elementName = string.IsNullOrEmpty(property.GetCustomAttribute()?.ElementName) ? property.Name : property.GetCustomAttribute()?.ElementName; if (!property.PropertyType.IsClass && reader.HasAttributes) throw new XmlException($"Unexpected attributes found on XML element '{elementName}'"); if (property.GetCustomAttribute() != null && property.GetCustomAttribute() == null) throw new XmlException($"Property '{property.Name}' must be assigned as an XML attribute."); if (assignedProperties.Contains(property.Name)) throw new XmlException($"Duplicate assignment for property '{property.Name}'"); assignedProperties.Add(property.Name); if (property.PropertyType.IsArray) { IList arrayList = null; var arrayItemType = property.PropertyType.GetElementType()!; var arrayItemTypes = property.GetCustomAttributes(typeof(XmlArrayItemAttribute)).OfType().Select(x => x.Type).ToList(); arrayItemTypes.Add(arrayItemType); reader.Read(); if (reader.NodeType == XmlNodeType.Text) { if (property.GetCustomAttribute() == null) throw new XmlException($"Element '{elementName}' must be an array, not a single value."); arrayList = Array.CreateInstance(arrayItemType, 1); arrayList[0] = ReadContentValue(reader, arrayItemType); property.SetValue(this, arrayList); } while (true) { if (!MoveToNextElement(reader, elementName)) break; if (arrayList == null) arrayList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(new[] { property.PropertyType.GetElementType() })); arrayList.Add(ReadElementValue(reader, arrayItemTypes)); } if (arrayList != null) { Array array = Array.CreateInstance(arrayItemType, arrayList.Count); arrayList.CopyTo(array, 0); property.SetValue(this, array); } } else property.SetValue(this, ReadElementValue(reader, property.PropertyType)); } } public void WriteXml(XmlWriter writer) => new XmlSerializer(this.GetType()).Serialize(writer, this); private static object ReadElementValue(XmlReader reader, Type type) { type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum) return Enum.Parse(type, reader.ReadElementContentAsString()); if (type == typeof(Guid)) return Guid.Parse(reader.ReadElementContentAsString()); if (type.IsClass && type != typeof(string)) { var serializer = new XmlSerializer(type, new XmlRootAttribute(reader.Name)); try { return serializer.Deserialize(reader); } catch (InvalidOperationException e) { if (e.InnerException == null) throw; throw e.InnerException; } } return reader.ReadElementContentAs(type, null); } private static object ReadContentValue(XmlReader reader, Type type) { type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum) return Enum.Parse(type, reader.ReadContentAsString()); if (type == typeof(Guid)) return Guid.Parse(reader.ReadContentAsString()); return reader.ReadContentAs(type, null); } private static object ReadElementValue(XmlReader reader, List types) { Type matchedType = types.FirstOrDefault(x => x.IsPrimitive || x == typeof(string) ? String.Equals(x.Name, reader.Name, StringComparison.OrdinalIgnoreCase) : x.Name == reader.Name); if (matchedType == null) throw new XmlException(types.Count > 1 ? $"Element '{reader.Name}' does not match any of the following:\r\n" + string.Join("\r\n", types.Select(x => x.Name)) : $"Element '{reader.Name}' does not match expected type '{types.FirstOrDefault()?.Name}'"); return ReadElementValue(reader, matchedType); } private bool MoveToNextElement(XmlReader reader, string enclosingElement) { if (reader.NodeType == XmlNodeType.EndElement && reader.Name == enclosingElement) { reader.ReadEndElement(); return false; } reader.Read(); while (reader.NodeType != XmlNodeType.Element) { if (reader.NodeType == XmlNodeType.EndElement && reader.Name == enclosingElement) { reader.ReadEndElement(); return false; } if (!reader.Read()) throw new XmlException("Unexpected end of XML document."); if (reader.NodeType == XmlNodeType.Text) throw new XmlException("Unexpected text."); } return true; } } } ================================================ FILE: Core/Services/Config.cs ================================================ using System; using System.Collections.Concurrent; using System.Globalization; using System.Runtime.Serialization; using System.Threading; using System.Xml; using System.Xml.Serialization; namespace Core { /* TODO: Interprocess Config [Serializable] public class InterConfigObject : ISerializable { private TType _localValue; private string _name; public InterConfigObject(TType value, string name) => (_localValue, _name) = (value, name); public void Set(TType value) { _localValue = value; } public TType Get() { return _localValue; } protected InterConfigObject(SerializationInfo info, StreamingContext context) { _localValue = (TType)info.GetValue(_name, typeof(TType)); } public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue(_name, _localValue); } } public class WizardConfig { public InterConfigObject VersionString = new InterConfigObject("0.7.5", nameof(VersionString)); } public static class Config { private static Thread _configThread = null; private static CancellationTokenSource _configThreadCancel = null; private static readonly BlockingCollection Queue = new BlockingCollection(); private static object _lockObject = new object(); public static Level Host; public static bool IsHost; public static bool IsWriter; private static void StartConfigThread(Level host, bool writer) { lock (_lockObject) { if (_configThread != null) throw new Exception("Only one logging instance allowed."); Host = host; IsHost = host == InterLink.ApplicationLevel.ToLevel(); IsWriter = writer; _configThreadCancel = new CancellationTokenSource(); _configThread = new Thread(ThreadLoop) { IsBackground = true, CurrentUICulture = CultureInfo.InvariantCulture }; _configThread.Start(); } } private static void EndConfigThread() { lock (_lockObject) { _configThreadCancel.Cancel(); if (!_configThread.Join(2000)) throw new TimeoutException("Log thread took too long to exit."); _configThread = null; } } private static void ThreadLoop() { foreach (var message in Queue.GetConsumingEnumerable(_configThreadCancel.Token)) { if (IsWriter) { try { XmlSerializer serializer = new XmlSerializer(typeof(Config)); using (XmlWriter writer = XmlWriter.Create(ConfigPath, new XmlWriterSettings() {Indent = true})) { serializer.Serialize(writer, Current); } } catch (Exception e) { Log.EnqueueExceptionSafe(e); } } if (!IsHost) { InterLink.ExecuteSafe() } Wrap.ExecuteSafe(() => Write(message)); } } */ } ================================================ FILE: Core/Services/Log.cs ================================================ using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Management; using System.Reflection; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Principal; using System.Text; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading; using Core.Miscellaneous; using JetBrains.Annotations; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; using ThreadState = System.Threading.ThreadState; namespace Core { public enum LogType { Info, Warning, Error, Critical, Debug, } #region Serializables [Serializable] public class SerializableTrace : ICloneable { public TraceFrame[] Frames { get; set; } public string Source { get; set; } public SerializableThread Thread { get; set; } public object Clone() { TraceFrame[] cloneFrames = new TraceFrame[Frames.Length]; Array.Copy(Frames, cloneFrames, Frames.Length); return new SerializableTrace() { Frames = cloneFrames, Source = Source, Thread = Thread }; } [JsonConstructor] public SerializableTrace(SerializableThread thread) => Thread = thread; public SerializableTrace(string source = null, int skipFrames = 0, int takeFrames = Int32.MaxValue) : this(null, source, skipFrames + 1, takeFrames) { } public SerializableTrace(Exception exception, string source = null, int skipFrames = 0, int takeFrames = Int32.MaxValue) { Source = source ?? Log.CurrentSource; Thread = new SerializableThread(System.Threading.Thread.CurrentThread); var stackTrace = exception == null ? new StackTrace(1 + skipFrames, true) : new StackTrace(exception, skipFrames, true); if (exception != null && stackTrace.FrameCount == 0) { stackTrace = new StackTrace(1 + skipFrames, true); takeFrames = Math.Min(takeFrames, 5); } var trace = new EnhancedStackTrace(stackTrace); var frames = (EnhancedStackFrame[])trace.GetFrames(); var frameList = new List(); int taken = 0; foreach (var frame in frames) { if (taken >= takeFrames) break; var methodName = frame.MethodInfo.Append(new StringBuilder(), false).ToString(); frameList.Add(new TraceFrame() { MethodName = methodName, FileName = Path.GetFileName(frame.GetFileName()), FileLineNumber = frame.GetFileLineNumber(), FileColumnNumber = frame.GetFileColumnNumber(), }); taken++; if (methodName.Contains("App.OnStartup(StartupEventArgs e)")) break; } Frames = frameList.ToArray(); } internal void Append(SerializableTrace append) { var frames = new TraceFrame[Frames.Length + append.Frames.Length + 1]; int i; for (i = 0; i < append.Frames.Length; i++) { frames[i].MethodName = append.Frames[i].MethodName; frames[i].FileName = Path.GetFileName(append.Frames[i].FileName); frames[i].FileLineNumber = append.Frames[i].FileLineNumber; frames[i].FileColumnNumber = append.Frames[i].FileColumnNumber; } frames[i].MethodName = $"[{append.Source}]"; i++; for (int j = 0; j < Frames.Length; j++, i++) { frames[i].MethodName = Frames[j].MethodName; frames[i].FileName = Path.GetFileName(Frames[j].FileName); frames[i].FileLineNumber = Frames[j].FileLineNumber; frames[i].FileColumnNumber = Frames[j].FileColumnNumber; } Frames = frames; } internal void Prepend(SerializableTrace prepend) { var frames = new TraceFrame[Frames.Length + prepend.Frames.Length + 1]; int i; for (i = 0; i < Frames.Length; i++) { frames[i].MethodName = Frames[i].MethodName; frames[i].FileName = Path.GetFileName(Frames[i].FileName); frames[i].FileLineNumber = Frames[i].FileLineNumber; frames[i].FileColumnNumber = Frames[i].FileColumnNumber; } frames[i].MethodName = $"[{Source}]"; i++; for (int j = 0; j < prepend.Frames.Length; j++, i++) { frames[i].MethodName = prepend.Frames[j].MethodName; frames[i].FileName = Path.GetFileName(prepend.Frames[j].FileName); frames[i].FileLineNumber = prepend.Frames[j].FileLineNumber; frames[i].FileColumnNumber = prepend.Frames[j].FileColumnNumber; } Frames = frames; Source = prepend.Source; } private string _stringValue { get; set; } = null; public override string ToString() => ToString(null); public string ToString([CanBeNull] string source) { if (_stringValue != null) return _stringValue; var threadString = Wrap.ExecuteSafe(() => { return ((Thread.ApartmentState == ApartmentState.STA || Thread.ManagedThreadId == 1) && !Thread.IsBackground && !Thread.IsThreadPoolThread) ? "" : Thread.ThreadState != System.Threading.ThreadState.Running && Thread.IsAlive ? $" ({Thread.ThreadState.ToString()} TID {Thread.ManagedThreadId})" : $" (TID {Thread.ManagedThreadId})"; }).Value; var traceBuilder = new StringBuilder($"[{source ?? Source ?? (Log.CurrentSource + threadString)}]"); var traceString = Wrap.ExecuteSafe(() => { foreach (var frame in Frames.Reverse()) { var name = Log.RemoveParameterTypes(frame.MethodName); if (String.IsNullOrWhiteSpace(name)) continue; string fileName = Path.GetFileName(frame.FileName); int lineNumber = frame.FileLineNumber; traceBuilder.Append((traceBuilder.Length == 0 ? "" : " >" + Environment.NewLine) + name + (String.IsNullOrWhiteSpace(fileName) ? "" : " in " + fileName) + (lineNumber == 0 ? "" : ":" + lineNumber)); } var traceString = traceBuilder.ToString(); if (Regex.IsMatch(traceString, @"Wrap\.Execute[a-zA-Z]*Safe")) { // Remove useless Polly execute lines return Regex.Replace(traceString, @".*Polly\.ResiliencePipeline\.Execute.*\n", ""); } return traceString; }).Value; _stringValue = traceString ?? traceBuilder.ToString(); return _stringValue; } [Serializable] public struct TraceFrame { public string MethodName { get; set; } [CanBeNull] public string FileName { get; set; } public int FileLineNumber { get; set; } public int FileColumnNumber { get; set; } } [Serializable] public struct SerializableThread { public ApartmentState ApartmentState { get; set; } public int ManagedThreadId { get; set; } public bool IsBackground { get; set; } public bool IsThreadPoolThread { get; set; } public ThreadState ThreadState { get; set; } public bool IsAlive { get; set; } public SerializableThread(Thread thread) { ApartmentState = thread.GetApartmentState(); ManagedThreadId = thread.ManagedThreadId; IsBackground = thread.IsBackground; IsThreadPoolThread = thread.IsThreadPoolThread; ThreadState = thread.ThreadState; IsAlive = thread.IsAlive; } } } [Serializable] public class SerializableException : Exception, IJsonOnDeserialized { public SerializableTrace Trace { get; set; } public string OriginalTraceString { get; set; } public Serializables.SerializableType OriginalType { get; set; } public new string Message { get; set; } [CanBeNull] public new SerializableException InnerException { get; set; } = null; [CanBeNull] public SerializableException[] AggregateInnerExceptions { get; set; } = null; [JsonIgnore] public bool WasDeserialized { get; set; } = false; [JsonConstructor] public SerializableException(Serializables.SerializableType originalType) => OriginalType = originalType; public void OnDeserialized() { SetMessage(this, Message); SetStackTrace(this, OriginalTraceString); typeof(Exception).GetField("_className", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(this, OriginalType?.Type?.ToString() ?? "SerializableException"); WasDeserialized = true; } public SerializableException([NotNull] Exception exception, string message = null) : this(exception, message, 0) { } private SerializableException([NotNull] Exception exception, string message = null, int skipFrames = 0) : base(message == null ? exception.Message : message.TrimEnd('.') + ": " + exception.Message) { Message = message == null ? exception.Message : message.TrimEnd('.') + ": " + exception.Message; if (exception is SerializableException serializableException) { this.Trace = (SerializableTrace)serializableException.Trace.Clone(); this.OriginalTraceString = serializableException.OriginalTraceString; this.OriginalType = serializableException.OriginalType; this.Message = message == null ? serializableException.Message : message.TrimEnd('.') + ": " + serializableException.Message; this.InnerException = serializableException.InnerException; this.AggregateInnerExceptions = serializableException.AggregateInnerExceptions; this.Trace.Append(new SerializableTrace("Duplicated at", 1, 1)); OnDeserialized(); WasDeserialized = false; return; } if (exception is Win32Exception win32Exception) { var rawMessage = new Win32Exception(win32Exception.NativeErrorCode).Message; if (rawMessage != win32Exception.Message) { // Message has been set manually by the Win32Exception constructor, so append the valuable win32 error message Message = Message.TrimEnd('.') + ": " + rawMessage; } } if (exception.InnerException != null) InnerException = new SerializableException(exception.InnerException, null, skipFrames + 1); if (exception is AggregateException aggregateException) AggregateInnerExceptions = aggregateException.InnerExceptions.Select(x => new SerializableException(x, null, skipFrames + 1)).ToArray(); Trace = new SerializableTrace(exception, null, skipFrames); OriginalType = new Serializables.SerializableType(exception.GetType()); OriginalTraceString = exception.StackTrace; OnDeserialized(); WasDeserialized = false; } private void SetStackTrace(Exception exception, string stackTrace) { var stackTraceField = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance); if (stackTraceField != null && stackTrace != null) stackTraceField.SetValue(exception, null); var remoteStackTraceField = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.NonPublic | BindingFlags.Instance); if (remoteStackTraceField != null && stackTrace != null) remoteStackTraceField.SetValue(exception, stackTrace + Environment.NewLine); var stackTraceObjectField = typeof(Exception).GetField("_stackTrace", BindingFlags.NonPublic | BindingFlags.Instance); if (stackTraceObjectField != null && stackTrace != null) stackTraceObjectField.SetValue(exception, null); } private void SetMessage(Exception exception, string message) { var messageField = typeof(Exception).GetField("_message", BindingFlags.NonPublic | BindingFlags.Instance); if (messageField != null && message != null) { messageField.SetValue(exception, message); } } } #endregion public static class Log { public const string GlobalLog = @"%PROGRAMDATA%\AME\Logs\Log.yml"; public static string CurrentSource { get; set; } = "Unknown"; public static ILogMetadata MetadataSource = new LogMetadata(); public static string LogFileOverride { get; set; } = null; private static Thread _logThread = null; private static CancellationTokenSource _logThreadCancel = null; private static readonly BlockingCollection Queue = new BlockingCollection(); #region Public write methods public class LogOptions { public static LogOptions Default { get; } = new LogOptions() { LogFile = GlobalLog, }; public LogOptions() { } public LogOptions(string logFile) => LogFile = logFile; public LogOptions(Output.OutputWriter writer) => OutputWriter = writer; public LogOptions(string logFile, Output.OutputWriter writer) => (OutputWriter, LogFile) = (writer, logFile); public string LogFile { get; set; } public Output.OutputWriter OutputWriter { get; set; } public string SourceOverride { get; set; } = null; } public static void EnqueueSafe(LogType type, [NotNull] string message, [CanBeNull] SerializableTrace trace, params (string Name, object Value)[] data) => EnqueueSafe(type, message, trace,null, data); public static void EnqueueSafe(LogType type, [NotNull] string message, [CanBeNull] SerializableTrace trace, [CanBeNull] LogOptions options = null, params (string Name, object Value)[] data) { #if !DEBUG if (type == LogType.Debug) return; #endif if (options?.OutputWriter != null) { WriteSafe(type, message, trace, options, data); return; } CheckLogThread(); Queue.Add(new LogMessage { FilePath = options?.LogFile ?? LogOptions.Default.LogFile, Type = type, Message = message, Trace = trace, Time = DateTime.UtcNow, SourceOverride = options?.SourceOverride, Data = (data?.Length ?? 0) == 0 ? null : data.ToDictionary(tuple => tuple.Name, tuple => tuple.Value?.ToString() ?? "null") }); } public static void EnqueueExceptionSafe(Exception exception, params (string Name, object Value)[] data) => EnqueueExceptionSafe(exception, null, null,null, data); public static void EnqueueExceptionSafe(Exception exception, [CanBeNull] string message, params (string Name, object Value)[] data) => EnqueueExceptionSafe(exception, message, null,null, data); public static void EnqueueExceptionSafe(Exception exception, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) => EnqueueExceptionSafe(exception, null, options, source, data); public static void EnqueueExceptionSafe(Exception exception, [CanBeNull] string message, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) => EnqueueExceptionSafe(LogType.Error, exception, message, options, source, data); public static void EnqueueExceptionSafe(LogType type, Exception exception, params (string Name, object Value)[] data) => EnqueueExceptionSafe(type, exception, null, null,null, data); public static void EnqueueExceptionSafe(LogType type, Exception exception, [CanBeNull] string message, params (string Name, object Value)[] data) => EnqueueExceptionSafe(type, exception, message, null,null, data); public static void EnqueueExceptionSafe(LogType type, Exception exception, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) => EnqueueExceptionSafe(type, exception, null, options, source, data); public static void EnqueueExceptionSafe(LogType type, Exception exception, [CanBeNull] string message, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) { if (exception == null && message == null) return; if (exception == null) { EnqueueSafe(LogType.Error, $"[Unknown] " + message, new SerializableTrace(source, 1), options, data); return; } if (options?.OutputWriter != null) { WriteExceptionSafe(type, exception, message, options, source, data); return; } string exceptionMessage = exception.GetRealMessage(); if (message != null) exceptionMessage = message.TrimEnd('.') + ": " + exceptionMessage; var logMessage = new LogMessage() { Type = type, Message = $"[{exception.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + exceptionMessage, Trace = new SerializableTrace(exception, source ?? options?.SourceOverride, 1), Data = (data?.Length ?? 0) == 0 ? null : data.ToDictionary(tuple => tuple.Name, tuple => tuple.Value?.ToString() ?? "null"), FilePath = options?.LogFile ?? LogOptions.Default.LogFile, Time = DateTime.UtcNow, SourceOverride = source ?? options?.SourceOverride }; if (exception is SerializableException serializableException) { logMessage = new LogMessage() { Type = type, Message = $"[{serializableException.OriginalType.Type.ToString().Split(new[] { '.', '+' }).Last()}] " + (exceptionMessage), Trace = serializableException.Trace, Data = (data?.Length ?? 0) == 0 ? null : data.ToDictionary(tuple => tuple.Name, tuple => tuple.Value?.ToString() ?? "null"), FilePath = options?.LogFile ?? LogOptions.Default.LogFile, Time = DateTime.UtcNow, SourceOverride = source ?? options?.SourceOverride }; if (serializableException.AggregateInnerExceptions != null) logMessage.Nested = serializableException.AggregateInnerExceptions.Select(x => new LogMessage() { Type = null, Time = null, Trace = x.Trace, Message = $"[(Aggregate) {x.OriginalType.Type.ToString().Split(new[] { '.', '+' }).Last()}] " + x.GetRealMessage() }).ToArray(); if (serializableException.InnerException != null) { var innerMessage = new LogMessage() { Type = null, Time = null, Trace = serializableException.InnerException.Trace, Message = $"[(Inner) {serializableException.InnerException.OriginalType.Type.ToString().Split(new[] { '.', '+' }).Last()}] " + serializableException.InnerException.GetRealMessage() }; logMessage.Nested = logMessage.Nested == null ? new[] { innerMessage } : logMessage.Nested.Concat(new[] { innerMessage }).ToArray(); } } else if (exception is AggregateException aggregate) { logMessage.Nested = aggregate.InnerExceptions.Select(x => new LogMessage() { Type = null, Time = null, Trace = new SerializableTrace(x, null, 1), Message = $"[(Aggregate) {x.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + x.GetRealMessage()}).ToArray(); if (aggregate.InnerException != null && !aggregate.InnerExceptions.Any(x => x == aggregate.InnerException)) { var innerMessage = new LogMessage() { Type = null, Time = null, Trace = new SerializableTrace(aggregate.InnerException, null, 1), Message = $"[(Inner) {aggregate.InnerException.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + aggregate.InnerException.GetRealMessage() }; logMessage.Nested = logMessage.Nested.Concat(new[] { innerMessage }).ToArray(); } } else { if (exception.InnerException != null) { var innerMessage = new LogMessage() { Type = null, Time = null, Trace = new SerializableTrace(exception.InnerException, null, 1), Message = $"[(Inner) {exception.InnerException.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + exception.InnerException.GetRealMessage() }; logMessage.Nested = new[] { innerMessage }; } } CheckLogThread(); Queue.Add(logMessage); } public static void WriteIf(bool condition, LogType type, [NotNull] string message, [CanBeNull] SerializableTrace trace = null, params (string Name, object Value)[] data) { if (condition) WriteSafe(type, message, trace, null, data); } public static void WriteSafe(LogType type, [NotNull] string message, [CanBeNull] SerializableTrace trace, params (string Name, object Value)[] data) => WriteSafe(type, message, trace,null, data); public static void WriteSafe(LogType type, [NotNull] string message, [CanBeNull] SerializableTrace trace, [CanBeNull] LogOptions options = null, params (string Name, object Value)[] data) { #if !DEBUG if (type == LogType.Debug) return; #endif var exception = Wrap.ExecuteSafe(() => { var logMessage = new LogMessage { FilePath = options?.LogFile ?? LogOptions.Default.LogFile, Type = type, Message = message, Trace = trace, Time = DateTime.UtcNow, SourceOverride = options?.SourceOverride, Data = (data?.Length ?? 0) == 0 ? null : data.ToDictionary(tuple => tuple.Name, tuple => tuple.Value?.ToString() ?? "null") }; Write(logMessage, options?.OutputWriter); }); if (exception != null) Log.EnqueueExceptionSafe(exception, source: "Logger"); } public static void WriteExceptionIf(bool condition, Exception exception, params (string Name, object Value)[] data) => WriteExceptionIf(condition, exception, null, data); public static void WriteExceptionIf(bool condition, Exception exception, [CanBeNull] string message, params (string Name, object Value)[] data) { if (condition) WriteExceptionSafe(exception, message, null, null, data); } public static void WriteExceptionSafe(Exception exception, params (string Name, object Value)[] data) => WriteExceptionSafe(exception, null, null,null, data); public static void WriteExceptionSafe(Exception exception, [CanBeNull] string message, params (string Name, object Value)[] data) => WriteExceptionSafe(exception, message, null,null, data); public static void WriteExceptionSafe(Exception exception, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) => WriteExceptionSafe(exception, null, options, source, data); public static void WriteExceptionSafe(Exception exception, [CanBeNull] string message, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) => WriteExceptionSafe(LogType.Error, exception, message, options, source, data); public static void WriteExceptionSafe(LogType type, Exception exception, params (string Name, object Value)[] data) => WriteExceptionSafe(type, exception, null, null,null, data); public static void WriteExceptionSafe(LogType type, Exception exception, [CanBeNull] string message, params (string Name, object Value)[] data) => WriteExceptionSafe(type, exception, message, null,null, data); public static void WriteExceptionSafe(LogType type, Exception exception, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) => WriteExceptionSafe(type, exception, null, options, source, data); public static void WriteExceptionSafe(LogType type, Exception exception, [CanBeNull] string message, [CanBeNull] LogOptions options = null, string source = null, params (string Name, object Value)[] data) { if (exception == null && message == null) return; if (exception == null) { WriteSafe(LogType.Error, $"[Unknown] " + message, new SerializableTrace(source, 1), options, data); return; } string exceptionMessage = exception.GetRealMessage(); if (message != null) exceptionMessage = message.ToString().TrimEnd('.') + ": " + exceptionMessage; var logMessage = new LogMessage() { Type = type, Message = $"[{exception.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + exceptionMessage, Trace = new SerializableTrace(exception, source ?? options?.SourceOverride, 1), Data = (data?.Length ?? 0) == 0 ? null : data.ToDictionary(tuple => tuple.Name, tuple => tuple.Value?.ToString() ?? "null"), FilePath = options?.LogFile ?? LogOptions.Default.LogFile, Time = DateTime.UtcNow, SourceOverride = source ?? options?.SourceOverride }; if (exception is SerializableException serializableException) { logMessage = new LogMessage() { Type = type, Message = $"[{serializableException.OriginalType.Type.ToString().Split(new[] { '.', '+' }).Last()}] " + exceptionMessage, Trace = serializableException.Trace, Data = (data?.Length ?? 0) == 0 ? null : data.ToDictionary(tuple => tuple.Name, tuple => tuple.Value?.ToString() ?? "null"), FilePath = options?.LogFile ?? LogOptions.Default.LogFile, Time = DateTime.UtcNow, SourceOverride = source ?? options?.SourceOverride }; if (serializableException.AggregateInnerExceptions != null) logMessage.Nested = serializableException.AggregateInnerExceptions.Select(x => new LogMessage() { Type = null, Time = null, Trace = x.Trace, Message = $"[(Aggregate) {x.OriginalType.Type.ToString().Split(new[] { '.', '+' }).Last()}] " + x.GetRealMessage() }).ToArray(); if (serializableException.InnerException != null) { var innerMessage = new LogMessage() { Type = null, Time = null, Trace = serializableException.InnerException.Trace, Message = $"[(Inner) {serializableException.InnerException.OriginalType.Type.ToString().Split(new[] { '.', '+' }).Last()}] " + serializableException.InnerException.GetRealMessage() }; logMessage.Nested = logMessage.Nested == null ? new[] { innerMessage } : logMessage.Nested.Concat(new[] { innerMessage }).ToArray(); } } else if (exception is AggregateException aggregate) { logMessage.Nested = aggregate.InnerExceptions.Select(x => new LogMessage() { Type = null, Time = null, Trace = new SerializableTrace(x, null, 1), Message = $"[(Aggregate) {x.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + x.GetRealMessage()}).ToArray(); if (aggregate.InnerException != null && !aggregate.InnerExceptions.Any(x => x == aggregate.InnerException)) { var innerMessage = new LogMessage() { Type = null, Time = null, Trace = new SerializableTrace(aggregate.InnerException, null, 1), Message = $"[(Inner) {aggregate.InnerException.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + aggregate.InnerException.GetRealMessage() }; logMessage.Nested = logMessage.Nested.Concat(new[] { innerMessage }).ToArray(); } } else { if (exception.InnerException != null) { var innerMessage = new LogMessage() { Type = null, Time = null, Trace = new SerializableTrace(exception.InnerException, null, 1), Message = $"[(Inner) {exception.InnerException.GetType().ToString().Split(new[] { '.', '+' }).Last()}] " + exception.InnerException.GetRealMessage() }; logMessage.Nested = new[] { innerMessage }; } } var writeException = Wrap.ExecuteSafe(() => Write(logMessage, options?.OutputWriter)); if (writeException != null) Log.EnqueueExceptionSafe(exception, source: "Logger"); } private static string GetRealMessage(this Exception exception) { string exceptionMessage = exception.Message; if (exception is Win32Exception win32Exception) { var rawMessage = new Win32Exception(win32Exception.NativeErrorCode).Message; if (rawMessage != win32Exception.Message) { // Message has been set manually by the Win32Exception constructor, so append the valuable win32 error message exceptionMessage = exceptionMessage.TrimEnd('.') + ": " + rawMessage; } } return exceptionMessage; } /// /// Write header metadata to log file. This deletes any previous filePath file if present. ///

Note that any normal write/enqueue method will automatically write the metadata if this was /// not called previously. ///
public static void WriteMetadata(string filePath) { var exception = Wrap.ExecuteSafe(() => { var logMessage = new LogMessage { FilePath = filePath, Type = LogType.Info, Message = null, Trace = null, Time = default, Data = null }; Write(logMessage, null, true); }); if (exception != null) Log.EnqueueExceptionSafe(exception, source: "Logger"); } #endregion #region Public helper methods public static SerializableTrace GetCurrentTrace() => new SerializableTrace(); #endregion #region Private methods private static object _lockObject = new object(); private static void CheckLogThread() { lock (_lockObject) { if (_logThread != null && !_logThread.IsAlive) _logThread = null; if (_logThread == null) StartLoggerThread(); } } private static void StartLoggerThread() { lock (_lockObject) { if (_logThread != null) throw new Exception("Only one logging instance allowed."); _logThreadCancel = new CancellationTokenSource(); _logThread = new Thread(ThreadLoop) { IsBackground = true, CurrentUICulture = CultureInfo.InvariantCulture }; _logThread.Start(); } } private static void EndLoggerThread() { lock (_lockObject) { _logThreadCancel.Cancel(); if (!_logThread.Join(2000)) throw new TimeoutException("Log thread took too long to exit."); _logThread = null; } } private static void ThreadLoop() { foreach (var message in Queue.GetConsumingEnumerable(_logThreadCancel.Token)) { Wrap.ExecuteSafe(() => Write(message)); } } private static readonly ISerializer _serializer = new SerializerBuilder().WithTypeConverter(new Converters.DateTimeConverter()).WithTypeConverter(new Converters.TraceConverter()).WithTypeConverter(new Converters.StringConverter()).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull).Build(); private static void Write(LogMessage message, Output.OutputWriter outputWriter = null, bool metadataOnly = false) { Converters.TraceConverter.SourceOverride = message.SourceOverride; using var md5 = MD5.Create(); var path = Wrap.ExecuteSafe(() => { var fullPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(LogFileOverride ?? message.FilePath)); if (!Directory.Exists(Path.GetDirectoryName(fullPath))) Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); return fullPath; }); if (path.Failed) { EnqueueExceptionSafe(path.Exception, source: "Logger"); return; } byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(path.Value)); var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToUpperInvariant(); using (var mutex = new Mutex(false, "AME-File-" + hash)) { try { mutex.WaitOne(); if (outputWriter != null) { long lineNumber = File.Exists(path.Value) ? Wrap.ExecuteSafe(() => File.ReadLines(path.Value).LongCount() + 2, -1).Value : 2; outputWriter.WriteLineSafe(message.Type + $" | {Path.GetFileName(path.Value)}:{lineNumber}", message.Message); } int i; for (i = 0; i != 10; i++) { StreamWriter writer = null; try { string metadata = null; if (!File.Exists(path.Value)) { using (_ = File.Create(path.Value)) { } FileInfo fileInfo = new FileInfo(path.Value); FileSecurity fileSecurity = fileInfo.GetAccessControl(); fileSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, AccessControlType.Allow)); fileInfo.SetAccessControl(fileSecurity); MetadataSource.Construct(); metadata = MetadataSource.Serialize(_serializer); } else if (metadataOnly) { MetadataSource.Construct(); metadata = MetadataSource.Serialize(_serializer); } else { Wrap.ExecuteSafe(() => TrimLogFile(path.Value)); } writer = new StreamWriter(path.Value, !metadataOnly); if (metadata != null) writer.Write(metadata); if (!metadataOnly) { var serialized = _serializer.Serialize(message); if (serialized.Length > 10000) { serialized = _serializer.Serialize(new LogMessage() { Message = "Log serialization exceeded 10000 characters.\r\nMessage: " + (message.Message.Length > 5000 ? message.Message.Take(5000) + "..." : message.Message) + (message.Trace == null ? string.Empty : "\r\nSource: " + message.Trace.Source), Type = LogType.Warning, Time = message.Time, }); } writer.WriteLine("---"); // document separator writer.Write(serialized); } writer.Flush(); break; } catch (Exception e) { if (_logThreadCancel.IsCancellationRequested) return; writer?.Dispose(); if (i == 8 && e is UnauthorizedAccessException ex) { EnqueueExceptionSafe(ex, null, "Logger", ("Path", path.Value)); path.Value += "x"; } if (i == 9) EnqueueExceptionSafe(e, null, "Logger", ("Path", path.Value)); Thread.Sleep(100); } finally { writer?.Dispose(); } } } finally { mutex.ReleaseMutex(); } } } private static void TrimLogFile(string path) { const long maxBytes = 1 * 1024 * 1024; // 1MB var fileInfo = new FileInfo(path); if (fileInfo.Length > maxBytes) { Log.EnqueueSafe(LogType.Warning, "Log filesize exceeded 1MB.", new SerializableTrace("Logger")); const long bytesToRemoveMinimum = 100 * 1024; // 100KB const long startRemovingAtMinimum = 100 * 1024; // Start removing at ~100KB var remainingLog = new StringBuilder(); bool shouldClearFile = false; using (var reader = new StreamReader(path)) { string line; long totalBytesRead = 0; // Collect bytes until we reach the start of block to be removed. while ((line = reader.ReadLine()) != null) { totalBytesRead += Encoding.UTF8.GetByteCount(line + Environment.NewLine); if (totalBytesRead >= startRemovingAtMinimum && line.StartsWith("---")) { break; } if (totalBytesRead < (maxBytes - bytesToRemoveMinimum)) { remainingLog.AppendLine(line); } else { shouldClearFile = true; break; } } // Skip the block to be removed. Starts and ends with a line ---. long bytesReadDuringRemoval = 0; while (!shouldClearFile && (line = reader.ReadLine()) != null && (bytesReadDuringRemoval < bytesToRemoveMinimum || !line.StartsWith("---"))) { bytesReadDuringRemoval += Encoding.UTF8.GetByteCount(line + Environment.NewLine); } // Read the rest of the file if (!shouldClearFile && line != null) // Add the line beginning with --- to the remaining log { remainingLog.AppendLine(line); remainingLog.AppendLine(reader.ReadToEnd()); } else { shouldClearFile = true; } } if (shouldClearFile) { File.WriteAllText(path, string.Empty); } else { using (var writer = new StreamWriter(path)) { writer.Write(remainingLog.ToString()); } } } } internal static string RemoveParameterTypes(string name) { int firstPar = name.IndexOf('('); int lastPar = name.LastIndexOf(')'); if (firstPar == -1 || lastPar == -1) return name; var paramsString = name.Substring(firstPar + 1, lastPar - firstPar - 1); var paramsArray = new List(); var curParam = new StringBuilder(); int bracketCount = 0; foreach (var ch in paramsString) { if (ch == '<') { curParam.Append(ch); bracketCount++; } else if (ch == '>') { curParam.Append(ch); bracketCount--; } else if (ch == ',' && bracketCount == 0) { paramsArray.Add(curParam.ToString().Trim().Substring(curParam.ToString().Trim().LastIndexOf(' ') + 1)); curParam.Clear(); } else curParam.Append(ch); } if (curParam.Length > 0) paramsArray.Add(curParam.ToString().Trim().Substring(curParam.ToString().Trim().LastIndexOf(' ') + 1)); return name.Substring(0, firstPar + 1) + string.Join(", ", paramsArray) + ")"; } #endregion #region Definitions public interface ILogMetadata { public string Serialize(ISerializer serializer); public void Construct(); } [Serializable] public class LogMetadata : ILogMetadata { public DateTime CreationTime { get; set; } public string SystemMemory { get; set; } public int SystemThreads { get; set; } public void Construct() { SystemMemory = StringUtils.HumanReadableBytes(Win32.SystemInfoEx.GetSystemMemoryInBytes()); SystemThreads = Environment.ProcessorCount; CreationTime = DateTime.UtcNow; } public string Serialize(ISerializer serializer) => serializer.Serialize(this); } [Serializable] private class LogMessage { [NonSerialized] [YamlIgnore] public string FilePath; [CanBeNull] [YamlIgnore] public string SourceOverride { get; set; } public string Message { get; set; } [CanBeNull] public Dictionary Data { get; set; } [CanBeNull] public LogType? Type { get; set; } [CanBeNull] public DateTime? Time { get; set; } [CanBeNull] public LogMessage[] Nested { get; set; } [CanBeNull] public SerializableTrace Trace { get; set; } } #endregion #region Converters private static class Converters { internal class DateTimeConverter : IYamlTypeConverter { public bool Accepts(Type type) => type == typeof(DateTime); public object ReadYaml(IParser parser, Type type) { throw new NotImplementedException(); } public void WriteYaml(IEmitter emitter, object value, Type type) { if (value == null) return; var dateVal = ((DateTime)value).ToString("yyyy/MM/dd HH:mm:ss"); emitter.Emit(new Scalar(null, null, dateVal, ScalarStyle.Any, true, false)); } } internal class TraceConverter : IYamlTypeConverter { public static string SourceOverride { get; set; } = null; public bool Accepts(Type type) => type == typeof(SerializableTrace); public object ReadYaml(IParser parser, Type type) => null; public void WriteYaml(IEmitter emitter, object value, Type type) { if (!(value is SerializableTrace trace) || trace.Source == null) return; emitter.Emit(new Scalar(null, null, trace.ToString(SourceOverride), ScalarStyle.Literal, true, false)); } } internal class StringConverter : IYamlTypeConverter { public bool Accepts(Type type) => type == typeof(string); public object ReadYaml(IParser parser, Type type) => null; public void WriteYaml(IEmitter emitter, object value, Type type) { if (value == null) return; var text = value.ToString(); if (text.Contains('\n')) emitter.Emit(new Scalar(null, null, text, ScalarStyle.Literal, true, false)); else emitter.Emit(new Scalar(null, null, text, ScalarStyle.Any, true, false)); } } } #endregion } } ================================================ FILE: Core/Services/Output.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Threading; using Core.Miscellaneous; using Core.Exceptions; using JetBrains.Annotations; using YamlDotNet.Serialization; namespace Core { public static class Output { private static readonly IdManager _outputIdManager = new IdManager(); #region Public methods public static void WriteAll([NotNull] string type, [CanBeNull] string text) { foreach (var writer in _writers.Keys) { writer.WriteLineSafe(type, text); } } public static void FlushAll() { foreach (var writer in _writers.Keys) { if (_writers.TryGetValue(writer, out _)) writer.FlushSafe(); } } #endregion public static string[] SplitByLine(this string text, StringSplitOptions options = StringSplitOptions.None) { return text.Split(new[] { "\r\n", "\n" }, options); } public static IEnumerable SplitByLength(this string str, int maxLength) { int index = 0; while(index + maxLength < str.Length) { yield return str.Substring(index, maxLength); index += maxLength; } yield return str.Substring(index); } #region Definitions private static ConcurrentDictionary _locks = new ConcurrentDictionary(); private static readonly ConcurrentDictionary _writers = new ConcurrentDictionary(); private struct WriterLockPair { public List Writers; public ConcurrentDictionary LineStreams; public object Lock; public object BufferLock; } public class OutputWriter : IDisposable { public static readonly OutputWriter Null = new OutputWriter(); public short ID { get; } public string Name { get; } [NotNull] public Log.LogOptions LogOptions { get; } = new Log.LogOptions(); public DateTime StartTimeUtc { get; } public string OutputFile { get; private set; } private string _bufferFile { get; } internal long _flushStart = -1; internal long _flushEnd = -1; private Guid guid = Guid.NewGuid(); private OutputWriter() {} public OutputWriter([NotNull] string name, [NotNull] string outputFile, string logFile = Log.GlobalLog) { Name = name; ID = _outputIdManager.GenerateId(); StartTimeUtc = DateTime.UtcNow; LogOptions.LogFile = logFile; LogOptions.OutputWriter = this; OutputFile = Path.GetFullPath(Environment.ExpandEnvironmentVariables(outputFile)); var directory = Path.GetDirectoryName(OutputFile); var bufferFileName = Path.GetFileNameWithoutExtension(OutputFile) + "Buffer.txt"; _bufferFile = Path.Combine(directory!, bufferFileName); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory!); if (_locks.TryGetValue(OutputFile, out var lockPair)) lockPair.Writers.Add(this); else _locks.TryAdd(outputFile, new WriterLockPair() { Writers = new List() { this }, Lock = new object(), BufferLock = new object(), LineStreams = new ConcurrentDictionary() }); _writers.TryAdd(this, default); } public void WriteLineSafe([NotNull] string type, [CanBeNull] string text) { if (OutputFile == null) return; WriteBufferSafe(type, text); } public void WriteLineRawSafe([CanBeNull] string text) { if (OutputFile == null) return; WriteBufferSafe(null, text, true); } public void FlushSafe() { if (OutputFile == null) return; var exception = Wrap.ExecuteSafe(() => { var lockPair = _locks[OutputFile]; lock (lockPair.Lock) { bool writeHeader = File.Exists(OutputFile); using (FileStream fileStream = new FileStream(OutputFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { long totalCopiedBytes = 0; if (_flushStart > -1 && _flushEnd > -1) { if (fileStream.Length == _flushEnd) { totalCopiedBytes = -1; writeHeader = false; } else { fileStream.Seek(0, SeekOrigin.End); long bytesToCopy = _flushEnd - _flushStart; long currentReadPosition = _flushStart; long currentWritePosition = fileStream.Position; int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; while (bytesToCopy > 0) { fileStream.Seek(currentReadPosition, SeekOrigin.Begin); int bytesToRead = (int)Math.Min(bufferSize, bytesToCopy); int bytesRead = fileStream.Read(buffer, 0, bytesToRead); if (bytesRead == 0) break; fileStream.Seek(currentWritePosition, SeekOrigin.Begin); fileStream.Write(buffer, 0, bytesRead); bytesToCopy -= bytesRead; totalCopiedBytes += bytesRead; currentReadPosition += bytesRead; currentWritePosition += bytesRead; } if (totalCopiedBytes != 0) writeHeader = false; currentReadPosition = _flushStart + totalCopiedBytes; currentWritePosition = _flushStart; while (true) { fileStream.Seek(currentReadPosition, SeekOrigin.Begin); int bytesRead = fileStream.Read(buffer, 0, bufferSize); if (bytesRead == 0) break; fileStream.Seek(currentWritePosition, SeekOrigin.Begin); fileStream.Write(buffer, 0, bytesRead); currentWritePosition += bytesRead; currentReadPosition += bytesRead; } fileStream.SetLength(currentWritePosition); foreach (var writer in lockPair.Writers) { if (writer._flushStart > _flushStart) { writer._flushStart -= totalCopiedBytes; writer._flushEnd -= totalCopiedBytes; } } } } fileStream.Seek(0, SeekOrigin.End); _flushStart = totalCopiedBytes == -1 ? _flushStart : fileStream.Position - totalCopiedBytes; try { if (writeHeader) { var headerBytes = Encoding.UTF8.GetBytes($"---" + Environment.NewLine); fileStream.Write(headerBytes, 0, headerBytes.Length); } if (File.Exists(_bufferFile)) { bool preserveLines = lockPair.Writers.Count > 1; lock (lockPair.BufferLock) { List streamsToUpdate = null; foreach (var lineStream in lockPair.LineStreams.Keys) { if (lineStream.Writer == this && lineStream.endPos != -1 && lineStream.startPos != -1) { lineStream.startPos = -1; lineStream.endPos = -1; lineStream.Flushed = true; } else { if (streamsToUpdate == null) streamsToUpdate = new List(); streamsToUpdate.Add(lineStream); } } using FileStream fs = new FileStream(_bufferFile, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); using StreamReaderWithPosition reader = new StreamReaderWithPosition(fs, Encoding.UTF8); using StreamWriter writer = new StreamWriter(fs); long writePosition = 0; bool foundMatch = false; string line; while ((line = reader.ReadLine()) != null) { if (GetID(line) == ID) { foundMatch = true; string bufferText = line.Substring($"[{ID.ToString()}]-".Length) + Environment.NewLine; byte[] writeText = Encoding.UTF8.GetBytes(bufferText); fileStream.Write(writeText, 0, writeText.Length); } else if (preserveLines) { if (!foundMatch) { writePosition = reader.Position; continue; } long readPosition = fs.Position; fs.Position = writePosition; writer.WriteLine(line); writer.Flush(); writePosition = fs.Position; fs.Position = readPosition; if (streamsToUpdate != null) { foreach (var lineStream in streamsToUpdate) { if (reader.Position == lineStream.startPos) { var size = lineStream.endPos - lineStream.startPos; lineStream.startPos = writePosition; lineStream.endPos = writePosition + size; } } } } } fs.SetLength(writePosition); } if (!preserveLines) Wrap.ExecuteSafe(() => File.Delete(_bufferFile)); } } finally { fileStream.Flush(); _flushEnd = fileStream.Length; } } } }); if (exception != null) Log.EnqueueExceptionSafe(exception, source: "Output Writer"); } #region Private methods private void WriteBufferSafe(string type, [CanBeNull] string text, bool raw = false) { var exception = Wrap.ExecuteSafe(() => { if (!_locks.TryGetValue(OutputFile, out var lockPair) || !_writers.TryGetValue(this, out _)) return; var lines = text?.SplitByLine() ?? new[] { "" }; lock (lockPair.BufferLock) { if (!_writers.TryGetValue(this, out _)) return; using var fileStream = new FileStream(_bufferFile, FileMode.Append); foreach (var line in lines) { string toWrite = $"[{ID}]-{(raw ? null : $"[{type} | {DateTime.UtcNow:HH:mm:ss}] ")}{line}" + Environment.NewLine; var encodedText = Encoding.UTF8.GetBytes(toWrite); fileStream.Write(encodedText, 0, encodedText.Length); } fileStream.Flush(); } }); if (exception != null) Log.EnqueueExceptionSafe(exception, source: "Output Writer"); } private void InsertBufferSafe(string type, [CanBeNull] string text, LineStream lineStream) { var exception = Wrap.ExecuteSafe(() => { if (!_locks.TryGetValue(OutputFile, out var lockPair) || !_writers.TryGetValue(this, out _)) return; var lines = text?.SplitByLine(); lock (lockPair.BufferLock) { if (!_writers.TryGetValue(this, out _)) return; if (lineStream.startPos != -1 && lineStream.endPos != -1) { using var fileStream = new FileStream(_bufferFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); if (lines != null) { string formattedText = string.Empty; foreach (var line in lines) formattedText += $"[{ID}]-[{type} | {DateTime.UtcNow:HH:mm:ss}] {line}" + Environment.NewLine; var encoded = Encoding.UTF8.GetBytes(formattedText); var shift = encoded.Length - (lineStream.endPos - lineStream.startPos); if (shift != 0) { ShiftBytes(fileStream, lineStream.endPos, shift); foreach (var stream in lockPair.LineStreams.Keys) { if (stream != lineStream && stream.startPos != -1 && stream.endPos != -1 && stream.startPos > lineStream.startPos) { stream.startPos += shift; stream.endPos += shift; } } } fileStream.Seek(lineStream.startPos, SeekOrigin.Begin); fileStream.Write(encoded, 0, encoded.Length); } else { var shift = -(lineStream.endPos - lineStream.startPos); if (shift != 0) { ShiftBytes(fileStream, lineStream.endPos, shift); foreach (var stream in lockPair.LineStreams.Keys) { if (stream != lineStream && stream.startPos != -1 && stream.endPos != -1 && stream.startPos > lineStream.startPos) { stream.startPos += shift; stream.endPos += shift; } } } fileStream.Seek(lineStream.startPos, SeekOrigin.Begin); } fileStream.Flush(); lineStream.endPos = fileStream.Position; } else if (lines != null) { using var fileStream = new FileStream(_bufferFile, FileMode.Append); lineStream.startPos = fileStream.Position; foreach (var line in lines) { string toWrite = $"[{ID}]-[{type} | {DateTime.UtcNow:HH:mm:ss}] {line}" + Environment.NewLine; var encodedText = Encoding.UTF8.GetBytes(toWrite); fileStream.Write(encodedText, 0, encodedText.Length); } fileStream.Flush(); lineStream.endPos = fileStream.Position; } } }); if (exception != null) Log.EnqueueExceptionSafe(exception, source: "Output Writer"); } private static void ShiftBytes(FileStream fs, long endPos, long shift) { const int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; long pos = endPos; if (shift < 0) { while (pos < fs.Length) { int toRead = pos + bufferSize > fs.Length ? (int)(fs.Length - pos) : bufferSize; fs.Position = pos; fs.Read(buffer, 0, toRead); fs.Position = pos + shift; fs.Write(buffer, 0, toRead); pos += toRead; } fs.SetLength(fs.Length + shift); } else if (shift > 0) { pos = fs.Length; fs.SetLength(pos + shift); while (pos > endPos) { int toRead = (pos - endPos) < bufferSize ? (int)(pos - endPos) : bufferSize; fs.Position = pos - toRead; fs.Read(buffer, 0, toRead); fs.Position = pos - toRead + shift; fs.Write(buffer, 0, toRead); pos -= toRead; } } } [CanBeNull] private static int? GetID(string line) { try { int startIdx = line.IndexOf('[') + 1; int endIdx = line.IndexOf(']'); if (startIdx > 0 && endIdx > startIdx) { string idStr = line.Substring(startIdx, endIdx - startIdx); if (int.TryParse(idStr, out int result)) { return result; } } return null; } catch { return null; } } #endregion public void Dispose() { if (OutputFile == null) return; _writers.TryRemove(this, out _); FlushSafe(); if (_locks.TryGetValue(OutputFile, out var lockPair)) { lock (lockPair.BufferLock) { lockPair.Writers.Remove(this); if (lockPair.Writers.Count == 0) Wrap.ExecuteSafe(() => File.Delete(_bufferFile)); } foreach (var lineStream in lockPair.LineStreams.Keys.Where(x => x.Writer == this)) lockPair.LineStreams.TryRemove(lineStream, out _); } else Log.EnqueueSafe(LogType.Error, "Lock not found.", new SerializableTrace("Output Writer")); _outputIdManager.ReleaseId(ID); } public class LineStream : IDisposable { public readonly OutputWriter Writer; private readonly string _type; internal long startPos = -1; internal long endPos = -1; public LineStream([NotNull] OutputWriter writer, string type) { (Writer, _type) = (writer, type); if (Writer.OutputFile == null) return; if (!_locks.TryGetValue(Writer.OutputFile, out var lockPair)) throw new UnexpectedException("Lock not found."); lockPair.LineStreams.TryAdd(this, new byte()); } public bool Flushed { get; set; } = false; public void WriteSafe([CanBeNull] string text) { if (Writer.OutputFile == null) return; if (isDisposed) throw new ObjectDisposedException(nameof(LineStream)); if (!_writers.TryGetValue(Writer, out _)) return; Writer.InsertBufferSafe(_type, text ?? string.Empty, this); Flushed = false; } public void Erase() { if (Writer.OutputFile == null) return; if (isDisposed) throw new ObjectDisposedException(nameof(LineStream)); if (!_writers.TryGetValue(Writer, out _)) return; if (!Flushed) Writer.InsertBufferSafe(_type, null, this); } private bool isDisposed = false; public void Dispose() { if (isDisposed) return; isDisposed = true; if (Writer.OutputFile == null) return; if (_locks.TryGetValue(Writer.OutputFile, out var lockPair)) lockPair.LineStreams.TryRemove(this, out _); } } } #endregion } } ================================================ FILE: Core/Utilities/IdManagers.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace Core { public class IdManager { private readonly SortedSet usedIds = new SortedSet(); private readonly object padlock = new object(); public short GenerateId() { lock (padlock) { short maxId = usedIds.Count > 0 ? usedIds.Max() : (short)0; for (short i = 1; i <= maxId + 1; i++) { if (usedIds.Add(i)) { return i; } } throw new InvalidOperationException("No available Ids left."); } } public void ReleaseId(short id) { lock (padlock) { usedIds.Remove(id); } } } } ================================================ FILE: Core/Utilities/StringUtils.cs ================================================ using System; namespace Core { public class StringUtils { public static string HumanReadableBytes(ulong input) { double size = input; string[] suffixes = { "B", "KB", "MB", "GB", "TB", "PB" }; int order = 0; while (size >= 1024 && order < suffixes.Length - 1) { order++; size = size / 1024; } double roundedAndFormattedSize = Math.Round((double)size, 0); return $"{roundedAndFormattedSize} {suffixes[order]}"; } public static string HumanReadableDiskSize(long input) { double dividedSize = input; string suffix = ""; foreach (var sizeSuffix in new[] { "KB", "MB", "GB", "TB", "PB" }) { dividedSize /= 1000; if (dividedSize < 1000) { suffix = sizeSuffix; break; } } if (dividedSize < 8) { var result = (Math.Abs((dividedSize * 10.0) - (Math.Floor(dividedSize + 0.5) * 10.0)) < 0.5) ? Math.Truncate((double)dividedSize) : Math.Truncate((double)(dividedSize * 10)) / 10; return result + " " + suffix; } else { uint t = (uint)dividedSize; t--; t |= t >> 1; t |= t >> 2; t |= t >> 4; t |= t >> 8; t |= t >> 16; t++; var result = (Math.Abs(1.0f - (dividedSize / (double)t)) < 0.05f) ? (long)t : (long)dividedSize; return result + " " + suffix; } } } } ================================================ FILE: Core/Utilities/Wrap.cs ================================================ using System; using System.ComponentModel.DataAnnotations; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using JetBrains.Annotations; using Polly; using Polly.Fallback; using Polly.Retry; namespace Core { /// /// Class for wrapping code with resilience strategies such as retries, fallbacks, and timeouts. ///

This class also contains safe execution methods like ExecuteSafe to ignore exceptions in cases where the success of an operation is not critical. ///
/// Wrap.Retry().Execute(() => your code here...) public static class Wrap { #region Pipelines private static ResiliencePipeline _retryExponential = null; public static ResiliencePipeline RetryExponential { get => _retryExponential ??= new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions() { MaxRetryAttempts = 3, BackoffType = DelayBackoffType.Exponential, UseJitter = true, Delay = TimeSpan.FromMilliseconds(1000) }).Build(); } /// /// Retries operation. ///
Will NOT retry upon an OperationCanceledException. ///
/// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline Retry([Range(1, 1000)] int attempts = 3, int millisecondsDelay = 1500) => (attempts != 1 ? new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { // By default it retries on any exception except OperationCanceledException. //ShouldHandle = arg => arg.Outcome.Exception != null ? PredicateResult.True() : PredicateResult.False(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) : new ResiliencePipelineBuilder()) // https://www.pollydocs.org/strategies/retry#defaults .Build(); /// /// Retries operation. ///
Will NOT retry upon an OperationCanceledException. ///
/// Timeout for when to cancel the operation. This applies to each attempt individually, and will trigger a cancellation on the CancellationToken. /// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline RetryWithTimeout(TimeSpan timeout, [Range(1, 1000)] int attempts = 3, int millisecondsDelay = 1500) => (attempts != 1 ? new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { // By default it retries on any exception except OperationCanceledException. //ShouldHandle = arg => arg.Outcome.Exception != null ? PredicateResult.True() : PredicateResult.False(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) : new ResiliencePipelineBuilder()).AddTimeout(timeout) .Build(); /// /// Retries extern client-helper.dll operation. ///
If the function does NOT return "Success", it will register as a fail. ///
/// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline HelperRetry([Range(2, 1000)] int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddFallback(new FallbackStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == "Success" ? PredicateResult.False() : PredicateResult.True(), FallbackAction = args => { // If this fallback code is reached, all retries failed. // Since Polly does not throw an exception if the retry // requirement (result == "Success") is not met, we force // an exception here. if (args.Outcome.Result != null) throw new Exception(args.Outcome.Result); else if (args.Outcome.Exception != null) throw args.Outcome.Exception; throw new Exception("HelperRetry fallback unexpected error."); } }) .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == "Success" ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) // https://www.pollydocs.org/strategies/retry#defaults .Build(); /// /// Retries extern client-helper.dll operation. ///
If the function does NOT return "Success", it will register as a fail. ///
/// Timeout for when to cancel the operation. This applies to each attempt individually, and will trigger a cancellation on the CancellationToken. /// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline HelperRetryWithTimeout(TimeSpan timeout, [Range(2, 1000)] int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddFallback(new FallbackStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == "Success" ? PredicateResult.False() : PredicateResult.True(), FallbackAction = args => { // If this fallback code is reached, all retries failed. // Since Polly does not throw an exception if the retry // requirement (result == "Success") is not met, we force // an exception here. if (args.Outcome.Result != null) throw new Exception(args.Outcome.Result); else if (args.Outcome.Exception != null) throw args.Outcome.Exception; throw new Exception("HelperRetry fallback unexpected error."); } }) .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == "Success" ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) // https://www.pollydocs.org/strategies/retry#defaults .Build(); /// /// Retries extern Win32 operation. ///
If the function does NOT return 0, it will register as a fail. ///
/// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline Win32IntegerRetry([Range(2, 1000)] int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == 0 ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) .Build(); /// /// Retries extern Win32 operation. ///
If the function does NOT return 0, it will register as a fail. ///
/// Timeout for when to cancel the operation. This applies to each attempt individually, and will trigger a cancellation on the CancellationToken. /// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline Win32IntegerRetryWithTimeout(TimeSpan timeout, [Range(2, 1000)] int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == 0 ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) .AddTimeout(timeout).Build(); /// /// Retries extern Win32 operation. ///
If the function does NOT return true, it will register as a fail. ///
/// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline Win32BoolRetry([Range(2, 1000)] int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) .Build(); /// /// Retries extern Win32 operation. ///
If the function does NOT return true, it will register as a fail. ///
/// Timeout for when to cancel the operation. This applies to each attempt individually, and will trigger a cancellation on the CancellationToken. /// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline Win32BoolRetryWithTimeout(TimeSpan timeout, [Range(2, 1000)] int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) .AddTimeout(timeout).Build(); /// /// Retries extern Win32 operation. ///
If the function returns INVALID_HANDLE_VALUE, it will register as a fail. ///
/// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline Win32HandleRetry(int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == null || arg.Outcome.Result.DangerousGetHandle() != Win32.INVALID_HANDLE_VALUE ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) .Build(); /// /// Retries extern Win32 operation. ///
If the function returns INVALID_HANDLE_VALUE, it will register as a fail. ///
/// Timeout for when to cancel the operation. This applies to each attempt individually, and will trigger a cancellation on the CancellationToken. /// Total number of attempts including the first call. /// Delay in milliseconds between attempts. public static ResiliencePipeline Win32HandleRetryWithTimeout(TimeSpan timeout, int attempts = 3, int millisecondsDelay = 1500) => new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { ShouldHandle = arg => arg.Outcome.Exception != null ? arg.Outcome.Exception.GetType() != typeof(OperationCanceledException) ? PredicateResult.True() : PredicateResult.False() : arg.Outcome.Result == null || arg.Outcome.Result.DangerousGetHandle() != Win32.INVALID_HANDLE_VALUE ? PredicateResult.False() : PredicateResult.True(), MaxRetryAttempts = attempts - 1, Delay = TimeSpan.FromMilliseconds(millisecondsDelay), }) .AddTimeout(timeout).Build(); #endregion #region Return Structs // Safe return value struct public struct SafeResult { [CanBeNull] public TResult Value; [CanBeNull] public Exception Exception; public bool Failed => Exception != null; public SafeResult(TResult result, Exception exception) { Value = result; Exception = exception; } internal SafeResult(SafeResult result) { Value = result.Value; Exception = result.Exception; } } // Safe return value struct without Result CanBeNull public struct SafeResult { public TResult Value; [CanBeNull] public Exception Exception; public bool Failed() => Exception != null; public SafeResult(TResult result, Exception exception) { Value = result; Exception = exception; } } // Safe return value struct public struct SafeFallbackResult { [CanBeNull] public TResult Value; [CanBeNull] public Exception Exception; [CanBeNull] public Exception FallbackException; public bool FallbackTriggered => Exception != null; public bool Failed => FallbackException != null; public SafeFallbackResult(TResult result, Exception exception, Exception fallbackException) { Value = result; Exception = exception; FallbackException = fallbackException; } internal SafeFallbackResult(SafeFallbackResult result) { Value = result.Value; Exception = result.Exception; FallbackException = result.FallbackException; } } // Safe return value struct without Result CanBeNull public struct SafeFallbackResult { public TResult Value; [CanBeNull] public Exception Exception; [CanBeNull] public Exception FallbackException; public bool FallbackTriggered => Exception != null; public bool Failed => FallbackException != null; public SafeFallbackResult(TResult result, Exception exception, Exception fallbackException) { Value = result; Exception = exception; FallbackException = fallbackException; } } public class DoubleException : Exception { [NotNull] public Exception PrimaryException; [CanBeNull] public Exception FallbackException; public bool FallbackTriggered => true; public bool Failed => FallbackException != null; public DoubleException(Exception primaryException, Exception fallbackException) { PrimaryException = primaryException; FallbackException = fallbackException; } } public class DoubleException : Exception { [NotNull] public Exception PrimaryException; [NotNull] public Exception FallbackException; public bool FallbackTriggered => true; public bool Failed => true; public DoubleException(Exception primaryException, Exception fallbackException) { PrimaryException = primaryException; FallbackException = fallbackException; } } #endregion #region Execution Methods #region Safe /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(ExecuteSafe(pipeline, operation, default!, logExceptions, logOptions)); /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] TResult errorResultValue, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { return new SafeResult(pipeline.Execute(operation), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(ExecuteSafe(pipeline, operation, default!, logExceptions, logOptions)); /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] TResult errorResultValue, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { return new SafeResult(pipeline.Execute(operation), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// The exception thrown, or null if successful. [CanBeNull] public static Exception ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Action operation, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { pipeline.Execute(operation); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return e; } return null; } /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe([NotNull] Func operation, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(ExecuteSafe(operation, default!, logExceptions, logOptions)); /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe([NotNull] Func operation, [NotNull] TResult errorResultValue, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { return new SafeResult(operation.Invoke(), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns null upon success, otherwise the exception thrown. [CanBeNull] public static Exception ExecuteSafe([NotNull] Action operation, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { operation.Invoke(); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return e; } return null; } #endregion #region Fallback Safe /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the return type.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) => new SafeFallbackResult(ExecuteWithFallbackSafe(pipeline, operation, fallback, default!, logExceptions, logOptions, usePipelineForFallback)); /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here..., "Error") [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, [NotNull] TResult errorResultValue, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { return new SafeFallbackResult(pipeline.Execute(operation), null, null); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { if (usePipelineForFallback) return new SafeFallbackResult(pipeline.Execute(fallback), primaryException, null); else return new SafeFallbackResult(fallback.Invoke(), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the return type.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) => new SafeFallbackResult(ExecuteWithFallbackSafe(pipeline, operation, fallback, default!, logExceptions, logOptions, usePipelineForFallback)); /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here..., "Error") [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, [NotNull] TResult errorResultValue, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { return new SafeFallbackResult(pipeline.Execute(operation), null, null); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { if (usePipelineForFallback) return new SafeFallbackResult(pipeline.Execute(fallback), primaryException, null); else return new SafeFallbackResult(fallback.Invoke(), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a DoubleException:
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [CanBeNull] public static DoubleException ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Action operation, [NotNull] Action fallback, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { pipeline.Execute(operation); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { if (usePipelineForFallback) pipeline.Execute(fallback); else fallback.Invoke(); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new DoubleException(primaryException, fallbackException); } return new DoubleException(primaryException, null); } return null; } /// /// Invokes operation safely, effectively try { operation.Invoke() } catch { }. If that fails, the same is attempted with fallback. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe([NotNull] Func operation, [NotNull] Func fallback, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeFallbackResult(ExecuteWithFallbackSafe(operation, fallback, default!, logExceptions, logOptions)); /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. If that fails, the same is attempted with fallback. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here..., "Error") [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe([NotNull] Func operation, [NotNull] Func fallback, [NotNull] TResult errorResultValue, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { return new SafeFallbackResult(operation.Invoke(), null, null); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { return new SafeFallbackResult(fallback.Invoke(), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }.

/// DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns null if the fallback was not needed, otherwise a DoubleException with the fallback and/or operation exception. /// await ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [CanBeNull] public static DoubleException ExecuteWithFallbackSafe([NotNull] Action operation, [NotNull] Action fallback, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { operation.Invoke(); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { fallback.Invoke(); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new DoubleException(primaryException, fallbackException); } return new DoubleException(primaryException, null); } return null; } #endregion #region Fallback Unsafe /// /// Executes pipeline.Execute(operation). If that fails, this executes pipeline.Execute(fallback), or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// await pipeline.ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static TResult ExecuteWithFallback(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { return pipeline.Execute(operation); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { if (usePipelineForFallback) return pipeline.Execute(fallback); else return fallback.Invoke(); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation). If that fails, this executes pipeline.Execute(fallback), or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// await pipeline.ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static TResult ExecuteWithFallback(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { return pipeline.Execute(operation); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { if (usePipelineForFallback) return pipeline.Execute(fallback); else return fallback.Invoke(); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation). If that fails, this executes pipeline.Execute(fallback), or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// await pipeline.ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static void ExecuteWithFallback(this ResiliencePipeline pipeline, [NotNull] Action operation, [NotNull] Action fallback, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { pipeline.Execute(operation); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, "Fallback: " + primaryException.Message, logOptions); try { if (usePipelineForFallback) pipeline.Execute(fallback); else fallback.Invoke(); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Invokes operation. If that fails, this invokes fallback. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// await ExecuteWithFallback(() => your code here..., () => your fallback code here...) [NotNull] public static TResult ExecuteWithFallback([NotNull] Func operation, [NotNull] Func fallback, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { return operation.Invoke(); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { return fallback.Invoke(); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Invokes operation. If that fails, this invokes fallback. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// await ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static void ExecuteWithFallback([NotNull] Action operation, [NotNull] Action fallback, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { operation.Invoke(); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); try { fallback.Invoke(); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } #endregion #region CancellationToken #region Safe /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(ExecuteSafe(pipeline, operation, default!, cancellationToken, logExceptions, logOptions)); /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] TResult errorResultValue, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeResult(pipeline.Execute(operation, cancellationToken), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(ExecuteSafe(pipeline, operation, default!, cancellationToken, logExceptions, logOptions)); /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] TResult errorResultValue, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeResult(pipeline.Execute(operation, cancellationToken), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Executes pipeline.Execute(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// The exception thrown, or null if successful. [CanBeNull] public static Exception ExecuteSafe(this ResiliencePipeline pipeline, [NotNull] Action operation, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); pipeline.Execute(operation, cancellationToken); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return e; } return null; } /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe([NotNull] Func operation, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(ExecuteSafe(operation, default!, cancellationToken, logExceptions, logOptions)); /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
[NotNull] public static SafeResult ExecuteSafe([NotNull] Func operation, [NotNull] TResult errorResultValue, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeResult(operation.Invoke(cancellationToken), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns null upon success, otherwise the exception thrown. [CanBeNull] public static Exception ExecuteSafe([NotNull] Action operation, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); operation.Invoke(cancellationToken); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return e; } return null; } #endregion #region Fallback Safe /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the return type.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) => new SafeFallbackResult(ExecuteWithFallbackSafe(pipeline, operation, fallback, default!, cancellationToken, logExceptions, logOptions, usePipelineForFallback)); /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here..., "Error") [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, [NotNull] TResult errorResultValue, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(pipeline.Execute(operation, cancellationToken), null, null); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); if (usePipelineForFallback) return new SafeFallbackResult(pipeline.Execute(fallback, cancellationToken), primaryException, null); else return new SafeFallbackResult(fallback.Invoke(cancellationToken), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the return type.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) => new SafeFallbackResult(ExecuteWithFallbackSafe(pipeline, operation, fallback, default!, cancellationToken, logExceptions, logOptions, usePipelineForFallback)); /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here..., "Error") [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, [NotNull] TResult errorResultValue, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(pipeline.Execute(operation, cancellationToken), null, null); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); if (usePipelineForFallback) return new SafeFallbackResult(pipeline.Execute(fallback, cancellationToken), primaryException, null); else return new SafeFallbackResult(fallback.Invoke(cancellationToken), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation) safely. If that fails, this executes pipeline.Execute(fallback) safely, or fallback.Invoke() if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a DoubleException:
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [CanBeNull] public static DoubleException ExecuteWithFallbackSafe(this ResiliencePipeline pipeline, [NotNull] Action operation, [NotNull] Action fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); pipeline.Execute(operation, cancellationToken); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); if (usePipelineForFallback) pipeline.Execute(fallback, cancellationToken); else fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new DoubleException(primaryException, fallbackException); } return new DoubleException(primaryException, null); } return null; } /// /// Invokes operation safely, effectively try { operation.Invoke() } catch { }. If that fails, the same is attempted with fallback. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe([NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeFallbackResult(ExecuteWithFallbackSafe(operation, fallback, default!, cancellationToken, logExceptions, logOptions)); /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }. If that fails, the same is attempted with fallback. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here..., "Error") [NotNull] public static SafeFallbackResult ExecuteWithFallbackSafe([NotNull] Func operation, [NotNull] Func fallback, [NotNull] TResult errorResultValue, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(operation.Invoke(cancellationToken), null, null); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(fallback.Invoke(cancellationToken), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Invokes operation safely, similar to try { operation.Invoke() } catch { }.

/// DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns null if the fallback was not needed, otherwise a DoubleException with the fallback and/or operation exception. /// await ExecuteWithFallbackSafe(() => your code here..., () => your fallback code here...) [CanBeNull] public static DoubleException ExecuteWithFallbackSafe([NotNull] Action operation, [NotNull] Action fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); operation.Invoke(cancellationToken); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new DoubleException(primaryException, fallbackException); } return new DoubleException(primaryException, null); } return null; } #endregion #region Fallback Unsafe /// /// Executes pipeline.Execute(operation). If that fails, this executes pipeline.Execute(fallback), or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await pipeline.ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static TResult ExecuteWithFallback(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return pipeline.Execute(operation, cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { if (usePipelineForFallback) return pipeline.Execute(fallback, cancellationToken); else return fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation). If that fails, this executes pipeline.Execute(fallback), or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await pipeline.ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static TResult ExecuteWithFallback(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return pipeline.Execute(operation, cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { if (usePipelineForFallback) return pipeline.Execute(fallback, cancellationToken); else return fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Executes pipeline.Execute(operation). If that fails, this executes pipeline.Execute(fallback), or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await pipeline.ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static void ExecuteWithFallback(this ResiliencePipeline pipeline, [NotNull] Action operation, [NotNull] Action fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); pipeline.Execute(operation, cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { if (usePipelineForFallback) pipeline.Execute(fallback, cancellationToken); else fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Invokes operation. If that fails, this invokes fallback. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await ExecuteWithFallback(() => your code here..., () => your fallback code here...) [NotNull] public static TResult ExecuteWithFallback([NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return operation.Invoke(cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { return fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Invokes operation. If that fails, this invokes fallback. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await ExecuteWithFallback(() => your code here..., () => your fallback code here...) public static void ExecuteWithFallback([NotNull] Action operation, [NotNull] Action fallback, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); operation.Invoke(cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } #endregion #endregion #region Async #region Safe /// /// Executes pipeline.ExecuteAsync(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
/// await pipeline.ExecuteSafeAsync(async token => your async code here...) [ItemNotNull] public static async Task> ExecuteSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(await ExecuteSafeAsync(pipeline, operation, default!, cancellationToken, logExceptions, logOptions)); /// /// Executes pipeline.ExecuteAsync(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
/// await pipeline.ExecuteSafeAsync(async token => your async code here..., "Error") [ItemNotNull] public static async Task> ExecuteSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, [NotNull] TResult errorResultValue, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeResult(await pipeline.ExecuteAsync(operation, cancellationToken), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Executes pipeline.ExecuteAsync(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
/// await pipeline.ExecuteSafeAsync(async token => your async code here...) [ItemNotNull] public static async Task> ExecuteSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(await ExecuteSafeAsync(pipeline, operation, default!, cancellationToken, logExceptions, logOptions)); /// /// Executes pipeline.ExecuteAsync(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
/// await pipeline.ExecuteSafeAsync(async token => your async code here..., "Error") [ItemNotNull] public static async Task> ExecuteSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, [NotNull] TResult errorResultValue, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeResult(await pipeline.ExecuteAsync(operation, cancellationToken), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Executes pipeline.ExecuteAsync(operation) safely. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// The exception thrown, or null if successful. /// await pipeline.ExecuteSafeAsync(async token => your async code here...) [ItemCanBeNull] public static async Task ExecuteSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func operation, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); await pipeline.ExecuteAsync(operation, cancellationToken); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return e; } return null; } /// /// Invokes operation safely and asynchronously, similar to try { await operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteSafeAsync(async token => your async code here...) [ItemNotNull] public static async Task> ExecuteSafeAsync([NotNull] Func> operation, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeResult(await ExecuteSafeAsync(operation, default!, cancellationToken, logExceptions, logOptions)); /// /// Executes operation safely and asynchronously, similar to try { await operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteSafeAsync(async token => your async code here..., "Error") [ItemNotNull] public static async Task> ExecuteSafeAsync([NotNull] Func> operation, [NotNull] TResult errorResultValue, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeResult(await operation.Invoke(cancellationToken), null); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return new SafeResult(errorResultValue, e); } } /// /// Invokes operation safely and asynchronously, similar to try { await operation.Invoke() } catch { }. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns null upon success, otherwise the exception thrown. /// await ExecuteSafeAsync(async token => your async code here...) [ItemCanBeNull] public static async Task ExecuteSafeAsync([NotNull] Func operation, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); try { cancellationToken.ThrowIfCancellationRequested(); await operation.Invoke(cancellationToken); } catch (Exception e) { if (logExceptions) Log.EnqueueExceptionSafe(e, logOptions); return e; } return null; } #endregion #region Fallback Safe /// /// Executes pipeline.ExecuteAsync(operation, cancellationToken) safely. If that fails, this executes pipeline.ExecuteAsync(fallback, cancellationToken) safely, or fallback.Invoke(cancellationToken) if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. /// Specifies to run fallback without the supplied pipeline. ///
/// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the return type.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here...) [ItemNotNull] public static async Task> ExecuteWithFallbackSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, [NotNull] Func> fallback, bool usePipelineForFallback = true, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeFallbackResult(await ExecuteWithFallbackSafeAsync(pipeline, operation, fallback, default!, logExceptions, logOptions, usePipelineForFallback, cancellationToken)); /// /// Executes pipeline.ExecuteAsync(operation, cancellationToken) safely. If that fails, this executes pipeline.ExecuteAsync(fallback, cancellationToken) safely, or fallback.Invoke(cancellationToken) if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Result value when no value could be retrieved from the operation or fallback.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here..., "Error") [ItemNotNull] public static async Task> ExecuteWithFallbackSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, [NotNull] Func> fallback, [NotNull] TResult errorResultValue, bool logExceptions = false, Log.LogOptions logOptions = null, bool usePipelineForFallback = true, CancellationToken cancellationToken = default) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(await pipeline.ExecuteAsync(operation, cancellationToken), null, null); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); if (usePipelineForFallback) return new SafeFallbackResult(await pipeline.ExecuteAsync(fallback, cancellationToken), primaryException, null); else return new SafeFallbackResult(await fallback.Invoke(cancellationToken), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Executes pipeline.ExecuteAsync(operation, cancellationToken) safely. If that fails, this executes pipeline.ExecuteAsync(fallback, cancellationToken) safely, or fallback.Invoke(cancellationToken) if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the return type.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here...) [ItemNotNull] public static async Task> ExecuteWithFallbackSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, [NotNull] Func> fallback, bool usePipelineForFallback = true, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeFallbackResult(await ExecuteWithFallbackSafeAsync(pipeline, operation, fallback, default!, usePipelineForFallback, cancellationToken)); /// /// Executes await pipeline.ExecuteAsync(operation, cancellationToken) safely. If that fails, this executes await pipeline.ExecuteAsync(fallback, cancellationToken) safely, or await fallback.Invoke(cancellationToken) if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here..., "Error") [ItemNotNull] public static async Task> ExecuteWithFallbackSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, [NotNull] Func> fallback, [NotNull] TResult errorResultValue, bool usePipelineForFallback = true, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(await pipeline.ExecuteAsync(operation, cancellationToken), null, null); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); if (usePipelineForFallback) return new SafeFallbackResult(await pipeline.ExecuteAsync(fallback, cancellationToken), primaryException, null); else return new SafeFallbackResult(await fallback.Invoke(cancellationToken), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Executes await pipeline.ExecuteAsync(operation, cancellationToken) safely. If that fails, this executes await pipeline.ExecuteAsync(fallback, cancellationToken) safely, or await fallback.Invoke(cancellationToken) if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Specifies to run fallback without the supplied pipeline. /// Returns a DoubleException:
PrimaryException: Set to null if operation was successful, otherwise the exception from the operation.
FallbackException: Set to null if the fallback was successful or was not called, otherwise the exception from the fallback.
/// await pipeline.ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here...) [ItemCanBeNull] public static async Task ExecuteWithFallbackSafeAsync(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, bool usePipelineForFallback = true, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); await pipeline.ExecuteAsync(operation, cancellationToken); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); if (usePipelineForFallback) await pipeline.ExecuteAsync(fallback, cancellationToken); else await fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new DoubleException(primaryException, fallbackException); } return new DoubleException(primaryException, null); } return null; } /// /// Executes pipeline.ExecuteAsync(operation, cancellationToken) safely. If that fails, this executes pipeline.ExecuteAsync(fallback, cancellationToken) safely, or fallback.Invoke(cancellationToken) if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the default value for the result type.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here...) [ItemNotNull] public static async Task> ExecuteWithFallbackSafeAsync([NotNull] Func> operation, [NotNull] Func> fallback, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) => new SafeFallbackResult(await ExecuteWithFallbackSafeAsync(operation, fallback, default!, cancellationToken, logExceptions, logOptions)); /// /// Executes pipeline.ExecuteAsync(operation, cancellationToken) safely. If that fails, this executes pipeline.ExecuteAsync(fallback, cancellationToken) safely, or fallback.Invoke(cancellationToken) if usePipelineForFallback is specified. ///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Result value when no value could be retrieved from the operation.
This should not be null, instead omit this parameter to return a null (default) result value upon error. /// Returns a SafeFallbackResult structure:
Value: Set to the result upon success, otherwise the value specified by errorResultValue.
Exception: Set to null upon success, otherwise the exception thrown.
/// await ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here..., "Error") [ItemNotNull] public static async Task> ExecuteWithFallbackSafeAsync([NotNull] Func> operation, [NotNull] Func> fallback, [NotNull] TResult errorResultValue, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(await operation.Invoke(cancellationToken), null, null); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); return new SafeFallbackResult(await fallback.Invoke(cancellationToken), primaryException, null); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new SafeFallbackResult(errorResultValue, primaryException, fallbackException); } } } /// /// Executes code safely and asynchronously, effectively try { await operation.Invoke(cancellationToken) } catch { }.

///

DOES NOT THROW EXCEPTION UPON FAILURE. ///
/// Returns null if the fallback was not needed, otherwise a DoubleException with the fallback and/or operation exception. /// await ExecuteWithFallbackSafeAsync(async token => your async code here..., async token => your async fallback code here...) [ItemCanBeNull] public static async Task ExecuteWithFallbackSafeAsync([NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); await operation.Invoke(cancellationToken); } catch (Exception primaryException) { if (logExceptions && !(primaryException is OperationCanceledException)) Log.EnqueueExceptionSafe(primaryException, logOptions); try { cancellationToken.ThrowIfCancellationRequested(); await fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); return new DoubleException(primaryException, fallbackException); } return new DoubleException(primaryException, null); } return null; } #endregion Fallback Safe #region Fallback Unsafe /// /// Executes pipeline.ExecuteAsync(operation). If that fails, this executes pipeline.ExecuteAsync(fallback) or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await pipeline.ExecuteWithFallbackAsync(async token => your async code here..., async token => your async fallback code here...) public static async Task ExecuteWithFallbackAsync(this ResiliencePipeline pipeline, [NotNull] Func> operation, [NotNull] Func> fallback, bool usePipelineForFallback = true, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return await pipeline.ExecuteAsync(operation, cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { if (usePipelineForFallback) return await pipeline.ExecuteAsync(fallback, cancellationToken); else return await fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Executes pipeline.ExecuteAsync(operation). If that fails, this executes pipeline.ExecuteAsync(fallback) or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await pipeline.ExecuteWithFallbackAsync(async token => your async code here..., async token => your async fallback code here...) public static async Task ExecuteWithFallbackAsync(this ResiliencePipeline pipeline, [NotNull] Func operation, [NotNull] Func fallback, bool usePipelineForFallback = true, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); await pipeline.ExecuteAsync(operation, cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { if (usePipelineForFallback) await pipeline.ExecuteAsync(fallback, cancellationToken); else await fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Executes pipeline.ExecuteAsync(operation). If that fails, this executes pipeline.ExecuteAsync(fallback) or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await ExecuteWithFallbackAsync(async token => your async code here..., async token => your async fallback code here...) public static async Task ExecuteWithFallbackAsync([NotNull] Func> operation, [NotNull] Func> fallback, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); return await operation.Invoke(cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { return await fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } /// /// Executes pipeline.ExecuteAsync(operation). If that fails, this executes pipeline.ExecuteAsync(fallback) or fallback.Invoke() if usePipelineForFallback is specified. /// /// This will always be the exception thrown if both operation and fallback fail, and contains the corresponding exceptions. /// This will be the exception thrown if cancellationToken is cancelled between execution of operation and fallback. /// await ExecuteWithFallbackAsync(async token => your async code here..., async token => your async fallback code here...) public static async Task ExecuteWithFallbackAsync([NotNull] Func operation, [NotNull] Func fallback, CancellationToken cancellationToken = default, bool logExceptions = false, Log.LogOptions logOptions = null) { ThrowIfNull(operation, nameof(operation)); ThrowIfNull(fallback, nameof(fallback)); try { cancellationToken.ThrowIfCancellationRequested(); await operation.Invoke(cancellationToken); } catch (Exception primaryException) { if (logExceptions) Log.EnqueueExceptionSafe(primaryException, logOptions); cancellationToken.ThrowIfCancellationRequested(); try { await fallback.Invoke(cancellationToken); } catch (Exception fallbackException) { if (logExceptions) Log.EnqueueExceptionSafe(fallbackException, "Fallback", logOptions); throw new DoubleException(primaryException, fallbackException); } } } #endregion Fallback Unsafe #endregion #endregion private static void ThrowIfNull(TArgument argument, string name) { Task.Run(() => { }); if (argument == null) throw new ArgumentNullException(name); } } public class SafeTask { public readonly Task> Task; public SafeTask(Func function, bool logExceptions, Log.LogOptions logOptions = null) => Task = new Task>(() => Wrap.ExecuteSafe(function, logExceptions, logOptions)); public void Start() => Task.Start(); } public class SafeTask { [ItemCanBeNull] public readonly Task Task; public SafeTask(Action action, bool logExceptions, Log.LogOptions logOptions = null) => Task = new Task(() => Wrap.ExecuteSafe(action, logExceptions, logOptions)); public void Start() => Task.Start(); public static Task Run(Action action, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafe(action, logExceptions, logOptions), CancellationToken.None); public static Task Run(Action action, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafe(action, logExceptions, logOptions), cancellationToken); public static Task> Run(Func function, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafe(function, logExceptions, logOptions), CancellationToken.None); public static Task> Run(Func function, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafe(function, logExceptions, logOptions), cancellationToken); public static Task Run(Func function, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafeAsync(token => function.Invoke(), CancellationToken.None, logExceptions, logOptions), CancellationToken.None); public static Task Run(Func function, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafeAsync(token => function.Invoke(), cancellationToken, logExceptions, logOptions), cancellationToken); public static Task> Run(Func> function, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafeAsync( token => function.Invoke(), CancellationToken.None, logExceptions, logOptions)); public static Task> Run(Func> function, CancellationToken cancellationToken, bool logExceptions = false, Log.LogOptions logOptions = null) => System.Threading.Tasks.Task.Run(() => Wrap.ExecuteSafeAsync(token => function.Invoke(), cancellationToken, logExceptions, logOptions), cancellationToken); } } ================================================ FILE: Interprocess/InterLink.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Pipes; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Security.AccessControl; using System.Security.Principal; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Win32.SafeHandles; using Core; using Core.Miscellaneous; using Expression = System.Linq.Expressions.Expression; using PipeOptions = System.IO.Pipes.PipeOptions; namespace Interprocess { #region Enums public enum Mode { SendOnly, ReceiveOnly, TwoWay } public enum ConnectionAction { /// /// Terminates the process. /// Exit, /// /// Pauses all connections, except incoming Progress reports and ShortMessages /// Pause, /// /// Permanently closes both incoming and outgoing connections. /// Close, } #endregion public static partial class InterLink { // 10MB private const int MaxMessageSize = 1024 * 1024 * 100; private const string PipePrefix = @"AME"; public static InternalLevel ApplicationLevel { get; private set; } = InternalLevel.Uninitialized; private static Mode _mode; private static int _hostPID = -1; #region Public Controls private static readonly object _launchLock = new object(); /// /// Launches a node. /// /// The level that should run launchMethod. /// The method used to launch the node. /// The level of the node to launch. /// The mode in which the node should run. /// An optional PID of a host to monitor. /// Specifies whether or not the node can be relaunched if it exits unexpectedly.

/// NOTE: This can only happen if parentLevel tries to execute a method on level. /// The process ID of the launched node. public static int LaunchNode(TargetLevel parentLevel, Expression> launchMethod, Level level, Mode mode, int hostPid, bool allowAutoRelaunch) { if (ApplicationLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("Cannot launch node when connection has not been initialized."); if (level == Level.Any || level == Level.Disposable) throw new InvalidOperationException($"{level} is not a valid for a node shutdowm."); lock (_launchLock) { var nodes = LevelController.GetRegisteredNodes(); var arguments = $"\"{Directory.GetCurrentDirectory()}\" Interprocess {level.ToString()} --Mode {mode.ToString()} --Nodes {$"Level={ApplicationLevel}:ProcessID={Process.GetCurrentProcess().Id}" + (nodes.Length > 0 ? "," : string.Empty) + string.Join(",", nodes.Select(x => $"Level={x.Level}:ProcessID={x.ProcessID}"))}" + (hostPid != -1 ? $" --Host {hostPid}" : string.Empty); var message = GetLambdaMessage(() => LaunchNode(GetLambdaMessage(launchMethod, parentLevel, arguments), level, ApplicationLevel, mode, hostPid, allowAutoRelaunch), parentLevel); ExecuteCore(message); try { if (!message.Processed.Wait(30000)) SetMessageResult(message, null, new TimeoutException($"Timeout reached (30000).")); } catch (OperationCanceledException e) { Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? e).Throw(); } if (message.Result.Exception != null) { if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); } var processId = (int)message.Result.Value!.Value!; SafeTask.Run(() => { uint exitCode = 258; using var handle = Win32.Process.OpenProcess(Win32.Process.ProcessAccessFlags.QueryLimitedInformation, false, processId); if (handle.IsInvalid) Log.EnqueueExceptionSafe(new Win32Exception()); else { while (Win32.Process.GetExitCodeProcess(handle, out exitCode) && exitCode == 259) Thread.Sleep(250); } OnNodeExit(level.ToInternalLevel(), processId, exitCode); CancelPendingOperations(level.ToTargetLevel(), new ApplicationException($"{level} node exited unexpectedly with exit code: " + exitCode)); }, true); LevelController.Register(level.ToInternalLevel(), processId, null, mode, hostPid); return processId; } } [InterprocessMethod(Level.Any)] private static int LaunchNode(MethodMessage launchMethod, Level level, InternalLevel caller, Mode mode, int hostPid, bool allowAutoRelaunch) { var method = launchMethod.Method.ParentClass.Type.GetMethod(launchMethod.Method.MethodName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public, null, launchMethod.Method.Parameters.Select(x => x.Type.Type).ToArray(), null)!; ThrowIfUnauthorizedMethodAccess(method, ApplicationLevel.ToTargetLevel(), caller); var launchCode = (Func)(_ => (int)method.Invoke(null, launchMethod.Method.Parameters.Select(x => x.Value).ToArray())); var processId = launchCode.Invoke(null); SafeTask.Run(() => { uint exitCode = 258; using var handle = Win32.Process.OpenProcess(Win32.Process.ProcessAccessFlags.QueryLimitedInformation, false, processId); if (handle.IsInvalid) Log.EnqueueExceptionSafe(new Win32Exception()); else { while (Win32.Process.GetExitCodeProcess(handle, out exitCode) && exitCode == 259) Thread.Sleep(250); } OnNodeExit(level.ToInternalLevel(), processId, exitCode); CancelPendingOperations(level.ToTargetLevel(), new ApplicationException($"{level} node exited unexpectedly with exit code: " + exitCode)); }, true); LevelController.Register(level.ToInternalLevel(), processId, allowAutoRelaunch ? launchCode : null, mode, hostPid); return processId; } public static int LaunchNode(Func launchCode, Level level, Mode mode, int hostPid, bool allowAutoRelaunch) { if (ApplicationLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("Cannot launch node when connection has not been initialized."); if (level == Level.Any || level == Level.Disposable) throw new InvalidOperationException($"{level} is not a valid for a node shutdowm."); lock (_launchLock) { var nodes = LevelController.GetRegisteredNodes(); var arguments = $"\"{Directory.GetCurrentDirectory()}\" Interprocess {level.ToString()} --Mode {mode.ToString()} --Nodes {$"Level={ApplicationLevel}:ProcessID={Process.GetCurrentProcess().Id}" + (nodes.Length > 0 ? "," : string.Empty) + string.Join(",", nodes.Select(x => $"Level={x.Level}:ProcessID={x.ProcessID}"))}" + (hostPid != -1 ? $" --Host {hostPid}" : string.Empty); var processId = launchCode.Invoke(arguments); SafeTask.Run(() => { uint exitCode = 258; using var handle = Win32.Process.OpenProcess(Win32.Process.ProcessAccessFlags.QueryLimitedInformation, false, processId); if (handle.IsInvalid) Log.EnqueueExceptionSafe(new Win32Exception()); else { while (Win32.Process.GetExitCodeProcess(handle, out exitCode) && exitCode == 259) Thread.Sleep(250); } OnNodeExit(level.ToInternalLevel(), processId, exitCode); CancelPendingOperations(level.ToTargetLevel(), new ApplicationException($"{level} node exited unexpectedly with exit code: " + exitCode)); }, true); LevelController.Register(level.ToInternalLevel(), processId, allowAutoRelaunch ? launchCode : null, mode, hostPid); return processId; } } public static void ShutdownNode(Level nodeLevel) { if (nodeLevel == Level.Any || nodeLevel == Level.Disposable) throw new InvalidOperationException($"{nodeLevel} is not a valid for a node shutdowm."); LevelController.Unregister(nodeLevel.ToInternalLevel()); MessageWriteQueue.Add(new ShutdownMessage(nodeLevel.ToInternalLevel(), ApplicationLevel)); } public static void ChangeMode(Mode mode) => _mode = mode; public static void RegisterDangerous(Level level, int processId) { // Exception check level.ToTargetLevel(); SafeTask.Run(() => { uint exitCode = 258; using var handle = Win32.Process.OpenProcess(Win32.Process.ProcessAccessFlags.QueryLimitedInformation, false, processId); if (handle.IsInvalid) Log.EnqueueExceptionSafe(new Win32Exception()); else { while (Win32.Process.GetExitCodeProcess(handle, out exitCode) && exitCode == 259) Thread.Sleep(250); } OnNodeExit(level.ToInternalLevel(), processId, exitCode); CancelPendingOperations(level.ToTargetLevel(), new ApplicationException($"{level} node exited unexpectedly with exit code: " + exitCode)); }, true); LevelController.Register(level.ToInternalLevel(), processId); } public static void SendText(TargetLevel level, string text, int millisecondsTimeout = 1000) { var textMessage = new TextMessage(level.ToInternalLevel(), ApplicationLevel) { Text = text, }; MessageWriteQueue.Add(textMessage); if (!textMessage.Processed.Wait(millisecondsTimeout)) { SetMessageResult(textMessage, null, new TimeoutException()); throw new TimeoutException(); } if (textMessage.Result.Exception != null) ExceptionDispatchInfo.Capture(textMessage.Result.Exception).Throw(); } public static void CancelPendingOperations(TargetLevel level, [NotNull] Exception exception) { foreach (var task in Tasks.Values.Where(x => x.Sent && x.TargetLevel == level.ToInternalLevel())) SetMessageResult(task, null, exception); } [ItemCanBeNull] public static Task SendTextSafe(TargetLevel level, string text, int millisecondsTimeout = 1000) => Wrap.ExecuteSafeAsync(token => SendTextAsync(level, text, millisecondsTimeout)); public static async Task SendTextAsync(TargetLevel level, string text, int millisecondsTimeout = 1000) { var textMessage = new TextMessage(level.ToInternalLevel(), ApplicationLevel) { Text = text, }; MessageWriteQueue.Add(textMessage); if (!await textMessage.Processed.WaitAsync(millisecondsTimeout)) { SetMessageResult(textMessage, null, new TimeoutException()); throw new TimeoutException(); } if (textMessage.Result.Exception != null) ExceptionDispatchInfo.Capture(textMessage.Result.Exception).Throw(); } [ItemCanBeNull] public static Task SendTextAsyncSafe(TargetLevel level, string text, int millisecondsTimeout = 1000) => Wrap.ExecuteSafeAsync(token => SendTextAsync(level, text, millisecondsTimeout)); #endregion #region Events public static event EventHandler TextReceived; public static event EventHandler NodeExitedUnexpectedly; public static event EventHandler NodeRegistered; #endregion #region Initializer public static async Task InitializeConnection(Level level, Mode mode, int host, (Interprocess.InterLink.InternalLevel Level, int ProcessID)[] nodes) { if (nodes != null) foreach (var node in nodes) Wrap.ExecuteSafe(() => LevelController.Register(node.Level, node.ProcessID), true); await InitializeConnection(level, mode, host); verificationThread.Join(); Environment.Exit(-4); } public static async Task InitializeConnection(Level currentLevel, Mode mode, int hostPid = -1) { if (ApplicationLevel != InternalLevel.Uninitialized) throw new InvalidOperationException("Cannot initialize connection twice."); if (senderCancel.IsCancellationRequested || receiverCancel.IsCancellationRequested) throw new InvalidOperationException("Cannot initialize connection after a cancellation. Use Pause() or Unpause() to control communication."); ApplicationLevel = currentLevel.ToInternalLevel(); _mode = mode; Log.CurrentSource = currentLevel + " Node"; Process host; try { host = hostPid == -1 ? null : Process.GetProcessById(hostPid); } catch (ArgumentException e) { Log.EnqueueExceptionSafe(e); Environment.Exit(-11); return; } _hostPID = hostPid; var pipeSecurity = new PipeSecurity(); var adminRule = new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), PipeAccessRights.Read | PipeAccessRights.Synchronize | PipeAccessRights.Write, AccessControlType.Allow); pipeSecurity.SetAccessRule(adminRule); // .NET 8: Remove async since we don't need to prep the serializer due to Source Generation. await Task.Run(() => { JsonSerializer.Deserialize(JsonSerializer.Serialize(new MethodMessage(InternalLevel.User, InternalLevel.Administrator) {Method = new SerializableMethod()}, _serializerOptions), _serializerOptions); JsonSerializer.Deserialize(JsonSerializer.Serialize(new MessageResult(Guid.Empty, InternalLevel.User, InternalLevel.Administrator, new Serializables.SerializableValue(typeof(string), "Test")), _serializerOptions), _serializerOptions); JsonSerializer.Deserialize(JsonSerializer.Serialize(new VerificationRequest() { CallerLevel = InternalLevel.User, IdToVerify = new Guid(), JsonHash = new byte[] { }, TargetLevel = InternalLevel.Administrator, Type = VerificationType.Message }, _serializerOptions), _serializerOptions); }); verificationThread = new Thread(() => VerificationThread(pipeSecurity)) { CurrentUICulture = CultureInfo.InvariantCulture, IsBackground = true }; verificationThread.Start(); sendResultThread = new Thread(SendResultThread) { CurrentUICulture = CultureInfo.InvariantCulture, IsBackground = true }; sendResultThread.Start(); senderThread = new Thread(SenderThread) { CurrentUICulture = CultureInfo.InvariantCulture, IsBackground = true }; senderThread.Start(); receiveResultThread = new Thread(() => ReceiveResultThread(pipeSecurity)) { CurrentUICulture = CultureInfo.InvariantCulture, IsBackground = true }; receiveResultThread.Start(); receiverThread = new Thread(() => ReceiverThread(pipeSecurity)) { CurrentUICulture = CultureInfo.InvariantCulture, IsBackground = true }; receiverThread.Start(); while (!host?.HasExited ?? false) { host.WaitForExit(); Thread.Sleep(100); } if (host != null) Environment.Exit(-10); } public static void CloseConnection(bool waitForExit) { CancelReceiver(); CancelSender(); if (waitForExit && (!senderThread.Join(250) || !sendResultThread.Join(250) || !receiverThread.Join(250) || !receiveResultThread.Join(250))) throw new TimeoutException("Connection took too long to close."); } public static async Task RegisterToTarget(TargetLevel level) { if (level == TargetLevel.Auto || level == TargetLevel.Disposable) throw new InvalidOperationException($"{level} is not a valid registration target."); if (level.ToInternalLevel() == ApplicationLevel) throw new InvalidOperationException("Cannot register to self."); var message = new NodeRegistrationMessage(level.ToInternalLevel(), ApplicationLevel) { Level = ApplicationLevel, ProcessID = Process.GetCurrentProcess().Id, }; MessageWriteQueue.Add(message); if (!await message.Processed.WaitAsync(500)) { SetMessageResult(message, null, new TimeoutException()); throw new TimeoutException("Registration timed out."); } if (message.Result.Exception != null) throw message.Result.Exception; var response = (int)message.Result.Value!.Value!; LevelController.Register(level.ToInternalLevel(), response); } #endregion #region Public Execution Methods public static void EnqueueSafe(Expression> operation, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, TargetLevel.Auto, enqueueTimeout, logExceptions); public static void EnqueueSafe(Expression>> operation, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, TargetLevel.Auto, enqueueTimeout, logExceptions); public static void EnqueueSafe(Expression> operation, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, TargetLevel.Auto, enqueueTimeout, logExceptions); public static void EnqueueSafe(Expression operation, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, TargetLevel.Auto, enqueueTimeout, logExceptions); public static void EnqueueSafe(Expression> operation, TargetLevel level, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, level, enqueueTimeout, logExceptions); public static void EnqueueSafe(Expression>> operation, TargetLevel level, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, level, enqueueTimeout, logExceptions); public static void EnqueueSafe(Expression> operation, TargetLevel level, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, level, enqueueTimeout, logExceptions); public static void EnqueueSafe(Expression operation, TargetLevel level, int enqueueTimeout, bool logExceptions) => EnqueueSafeCore(operation, level, enqueueTimeout, logExceptions); private static void EnqueueSafeCore(Expression operation, TargetLevel level, int enqueueTimeout, bool logExceptions) { if (enqueueTimeout < 1) throw new ArgumentException($"{nameof(enqueueTimeout)} must be greater than 0.", nameof(enqueueTimeout)); var message = GetLambdaMessageCore(operation, level); message.EnqueueTimeout = enqueueTimeout; message.Enqueued = true; message.LogExceptions = logExceptions; ExecuteCore(message); } public static Wrap.SafeResult ExecuteSafe(Expression> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteSafe(operation, TargetLevel.Auto, logExceptions, timeout); public static Wrap.SafeResult ExecuteSafe(Expression> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) => Wrap.ExecuteSafe(() => Execute(operation, level, logExceptions, timeout)); public static TResult Execute(Expression> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => Execute(operation, TargetLevel.Auto, logExceptions, timeout); public static TResult Execute(Expression> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) { var message = GetLambdaMessage(operation, level); ExecuteCore(message); try { if (!message.Processed.Wait(timeout)) SetMessageResult(message, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? e).Throw(); } if (message.Result.Exception != null) { if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); } return message.Result.Value == null ? default(TResult) : (TResult)message.Result.Value.Value; } public static Wrap.SafeResult ExecuteSafe(Expression>> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteSafe(operation, TargetLevel.Auto, logExceptions, timeout); public static Wrap.SafeResult ExecuteSafe(Expression>> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) => Wrap.ExecuteSafe(() => Execute(operation, level, logExceptions, timeout)); public static TResult Execute(Expression>> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => Execute(operation, TargetLevel.Auto, logExceptions, timeout); public static TResult Execute(Expression>> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) { var message = GetLambdaMessage(operation, level); ExecuteCore(message); try { if (!message.Processed.Wait(timeout)) SetMessageResult(message, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? e).Throw(); } if (message.Result.Exception != null) { if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); } return message.Result.Value == null ? default(TResult) : (TResult)message.Result.Value.Value; } public static Task> ExecuteSafeAsync(Expression> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteSafeAsync(operation, TargetLevel.Auto, logExceptions, timeout); public static Task> ExecuteSafeAsync(Expression> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) => Wrap.ExecuteSafeAsync(async token => await ExecuteAsync(operation, level, logExceptions, timeout)); public static Task ExecuteAsync(Expression> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteAsync(operation, TargetLevel.Auto, logExceptions, timeout); public static async Task ExecuteAsync(Expression> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) { var message = GetLambdaMessage(operation, level); ExecuteCore(message); try { if (!await message.Processed.WaitAsync(timeout)) SetMessageResult(message, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? e).Throw(); } if (message.Result.Exception != null) { if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); return default; } return message.Result.Value == null || typeof(TResult) == typeof(Task) ? default : (TResult)message.Result.Value.Value; } public static Task> ExecuteSafeAsync(Expression>> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteSafeAsync(operation, TargetLevel.Auto, logExceptions, timeout); public static Task> ExecuteSafeAsync(Expression>> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) => Wrap.ExecuteSafeAsync(async token => await ExecuteAsync(operation, level, logExceptions, timeout)); public static Task ExecuteAsync(Expression>> operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteAsync(operation, TargetLevel.Auto, logExceptions, timeout); public static async Task ExecuteAsync(Expression>> operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) { var message = GetLambdaMessage(operation, level); ExecuteCore(message); try { if (!await message.Processed.WaitAsync(timeout)) SetMessageResult(message, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? e).Throw(); } if (message.Result.Exception != null) { if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); return default; } return message.Result.Value == null ? default : (TResult)message.Result.Value.Value; } public static Exception ExecuteSafe(Expression operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteSafe(operation, TargetLevel.Auto, logExceptions, timeout); public static Exception ExecuteSafe(Expression operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) => Wrap.ExecuteSafe(() => Execute(operation, level, logExceptions, timeout)); public static void Execute(Expression operation, bool logExceptions = false, int timeout = Timeout.Infinite) => Execute(operation, TargetLevel.Auto, logExceptions, timeout); public static void Execute(Expression operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) { var message = GetLambdaMessage(operation, level); ExecuteCore(message); try { if (!message.Processed.Wait(timeout)) SetMessageResult(message, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? e).Throw(); } if (message.Result.Exception != null) { if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); } } public static Task ExecuteSafeAsync(Expression operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteSafeAsync(operation, TargetLevel.Auto, logExceptions, timeout); public static Task ExecuteSafeAsync(Expression operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) => Wrap.ExecuteSafeAsync(async token => await ExecuteAsync(operation, level, logExceptions, timeout)); public static Task ExecuteAsync(Expression operation, bool logExceptions = false, int timeout = Timeout.Infinite) => ExecuteAsync(operation, TargetLevel.Auto, logExceptions, timeout); public static async Task ExecuteAsync(Expression operation, TargetLevel level, bool logExceptions = false, int timeout = Timeout.Infinite) { var message = GetLambdaMessage(operation, level); ExecuteCore(message); try { if (!await message.Processed.WaitAsync(timeout)) SetMessageResult(message, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? e).Throw(); } if (message.Result.Exception != null) { if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); } } #region Disposable public static Wrap.SafeResult ExecuteDisposableSafe(TargetLevel parentLevel, Expression> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafe(() => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null)); public static Wrap.SafeResult ExecuteDisposableSafe(TargetLevel parentLevel, Expression>> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafe(() => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null)); public static Exception ExecuteDisposableSafe(TargetLevel parentLevel, Expression operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafe((Action)(() => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null))); public static Task> ExecuteDisposableSafeAsync(TargetLevel parentLevel, Expression> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafeAsync(async token => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null)); public static Task> ExecuteDisposableSafeAsync(TargetLevel parentLevel, Expression>> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafeAsync(async token => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null)); public static Task ExecuteDisposableSafeAsync(TargetLevel parentLevel, Expression operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafeAsync((Func)(async token => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null))); public static TResult ExecuteDisposable(TargetLevel parentLevel, Expression> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null); public static TResult ExecuteDisposable(TargetLevel parentLevel, Expression>> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null); public static void ExecuteDisposable(TargetLevel parentLevel, Expression operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null); public static Task ExecuteDisposableAsync(TargetLevel parentLevel, Expression> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null); public static Task ExecuteDisposableAsync(TargetLevel parentLevel, Expression>> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null); public static async Task ExecuteDisposableAsync(TargetLevel parentLevel, Expression operation, int timeout = 60000, bool logExceptions = false) => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, null); public static Wrap.SafeResult ExecuteDisposableSafe(TargetLevel parentLevel, Expression> launchMethod, Expression> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafe(() => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}"))); public static Wrap.SafeResult ExecuteDisposableSafe(TargetLevel parentLevel, Expression> launchMethod, Expression>> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafe(() => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}"))); public static Exception ExecuteDisposableSafe(TargetLevel parentLevel, Expression> launchMethod, Expression operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafe((Action)(() => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")))); public static Task> ExecuteDisposableSafeAsync(TargetLevel parentLevel, Expression> launchMethod, Expression> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafeAsync(async token => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}"))); public static Task> ExecuteDisposableSafeAsync(TargetLevel parentLevel, Expression> launchMethod, Expression>> operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafeAsync(async token => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}"))); public static Task ExecuteDisposableSafeAsync(TargetLevel parentLevel, Expression> launchMethod, Expression operation, int timeout = 60000, bool logExceptions = false) => Wrap.ExecuteSafeAsync((Func)(async token => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"Interprocess --ActivePath \"{Directory.GetCurrentDirectory()}\" Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")))); public static TResult ExecuteDisposable(TargetLevel parentLevel, Expression> launchMethod, Expression> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")); public static TResult ExecuteDisposable(TargetLevel parentLevel, Expression> launchMethod, Expression>> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")); public static void ExecuteDisposable(TargetLevel parentLevel, Expression> launchMethod, Expression operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCore(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")); public static Task ExecuteDisposableAsync(TargetLevel parentLevel, Expression> launchMethod, Expression> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")); public static Task ExecuteDisposableAsync(TargetLevel parentLevel, Expression> launchMethod, Expression>> operation, int timeout = 60000, bool logExceptions = false) => ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")); public static async Task ExecuteDisposableAsync(TargetLevel parentLevel, Expression> launchMethod, Expression operation, int timeout = 60000, bool logExceptions = false) => await ExecuteDisposableCoreAsync(parentLevel, GetLambdaMessage(operation, parentLevel), timeout, logExceptions, GetLambdaMessage(launchMethod, parentLevel, $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={parentLevel}:ProcessID={(ApplicationLevel == parentLevel.ToInternalLevel() ? Process.GetCurrentProcess().Id : LevelController.GetRegisteredNodes().First(x => x.Level == parentLevel.ToInternalLevel()).ProcessID)} --Host {Process.GetCurrentProcess().Id}")); #endregion #endregion internal struct Void { } #region Core Execution Methods private static TResult ExecuteDisposableCore(TargetLevel parentLevel, MethodMessage message, int timeout, bool logExceptions, MethodMessage launchMethod) { if (parentLevel == TargetLevel.Disposable) throw new ArgumentException("Invalid disposable parent level: " + parentLevel, nameof(parentLevel)); if (ApplicationLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("Connection must be initialized to call execution methods."); if (parentLevel.ToInternalLevel() == ApplicationLevel) { return NodeMethods.ExecuteDisposable(message, timeout, logExceptions, launchMethod); } var sendMessage = GetLambdaMessage(() => NodeMethods.ExecuteDisposable(message, timeout, logExceptions, launchMethod), parentLevel); MessageWriteQueue.Add(sendMessage); try { if (!sendMessage.Processed.Wait(timeout + 5000)) SetMessageResult(sendMessage, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(sendMessage.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(sendMessage.Result.Exception ?? senderException ?? e).Throw(); } if (sendMessage.Result.Exception != null) { if (sendMessage.Result.Exception.WasDeserialized) sendMessage.Result.Exception.Trace.Prepend(new SerializableTrace(null, 1, 3)); ExceptionDispatchInfo.Capture(sendMessage.Result.Exception).Throw(); } return (TResult)sendMessage.Result.Value!.Value; } private static async Task ExecuteDisposableCoreAsync(TargetLevel parentLevel, MethodMessage message, int timeout, bool logExceptions, MethodMessage launchMethod) { if (parentLevel == TargetLevel.Disposable) throw new ArgumentException("Invalid disposable parent level: " + parentLevel, nameof(parentLevel)); if (ApplicationLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("Connection must be initialized to call execution methods."); if (parentLevel.ToInternalLevel() == ApplicationLevel) { return await Task.Run(() => NodeMethods.ExecuteDisposable(message, timeout, logExceptions, launchMethod)); } var sendMessage = GetLambdaMessage(() => NodeMethods.ExecuteDisposable(message, timeout, logExceptions, launchMethod), parentLevel); MessageWriteQueue.Add(sendMessage); try { if (!await sendMessage.Processed.WaitAsync(timeout + 5000)) SetMessageResult(sendMessage, null, new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { if (logExceptions) Log.EnqueueExceptionSafe(sendMessage.Result.Exception ?? senderException ?? e); ExceptionDispatchInfo.Capture(sendMessage.Result.Exception ?? senderException ?? e).Throw(); } if (sendMessage.Result.Exception != null) { if (sendMessage.Result.Exception.WasDeserialized) sendMessage.Result.Exception.Trace.Prepend(new SerializableTrace(null, 1, 3)); ExceptionDispatchInfo.Capture(sendMessage.Result.Exception).Throw(); } return (TResult)sendMessage.Result.Value!.Value; } private static void ExecuteCore(MethodMessage message) { if (ApplicationLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("Connection must be initialized to call execution methods."); if (message.TargetLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("InternalLevel.Uninitialized is not a valid target level."); if (message.CallerLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("InternalLevel.Uninitialized is not a valid caller level."); if (_mode == Mode.ReceiveOnly) throw new UnauthorizedAccessException("Cannot send a message from a receive-only node."); if (message.Method == null) throw new InvalidOperationException("InterMessage Method property cannot be null."); try { foreach (var argument in message.Method.Parameters) { if (argument.Value != null && typeof(IInterObject).IsAssignableFrom(argument.Type.Type)) ((IInterObject)argument.Value).BeforeSend(message.TargetLevel); } } catch (Exception e) { SetMessageResult(message, null, new SerializableException(e)); } if (message.TargetLevel == ApplicationLevel) { Task.Run(() => { var result = Wrap.ExecuteSafe(() => { var result = new Serializables.SerializableValue(message.Method.Method.ReturnType, message.Method.Method.Invoke(null, message.Method.Parameters.Select(x => x.Value).ToArray())); if (result.Value is Task task) { task.GetAwaiter().GetResult(); if (task.GetType().IsGenericType) { dynamic taskOfTypeT = task; result = taskOfTypeT.Result == null ? null : new Serializables.SerializableValue(taskOfTypeT.Result.GetType(), taskOfTypeT.Result); } } return result; }); if (result.Failed) { if (result.Exception is TargetInvocationException invokeException && invokeException.InnerException != null) SetMessageResult(message, null, new SerializableException(invokeException.InnerException, $"({message.TargetLevel}) " + invokeException.InnerException.Message)); else SetMessageResult(message, null, new SerializableException(result.Exception, $"({message.TargetLevel}) " + result.Exception.Message)); } else SetMessageResult(message, new MessageResult(new Guid(), message.TargetLevel, message.CallerLevel, result.Value), null); message.Processed.Release(); }); return; } if (senderException != null) ExceptionDispatchInfo.Capture(senderException).Throw(); MessageWriteQueue.Add(message); } private static MethodMessage GetLambdaMessage(Expression> expression, TargetLevel targetLevel) => GetLambdaMessageCore(expression, targetLevel); private static MethodMessage GetLambdaMessage(Expression>> expression, TargetLevel targetLevel) => GetLambdaMessageCore(expression, targetLevel); private static MethodMessage GetLambdaMessage(Expression expression, TargetLevel targetLevel) => GetLambdaMessageCore(expression, targetLevel); private static MethodMessage GetLambdaMessage(Expression> expression, TargetLevel targetLevel, TArg argument) => GetLambdaMessageCore(expression, targetLevel, (typeof(TArg), argument)); private static MethodMessage GetLambdaMessage(Expression> expression, TargetLevel targetLevel, TArg argument) => GetLambdaMessageCore(expression, targetLevel, (typeof(TArg), argument)); private static MethodMessage GetLambdaMessageCore(Expression expression, TargetLevel targetLevel, params (Type Type, object Value)[] args) { if (ApplicationLevel == InternalLevel.Uninitialized) throw new InvalidOperationException("Connection must be initialized to call execution methods."); if (!(expression is LambdaExpression lambdaExpression)) throw new SerializationException("Unrecognized expression type: " + expression.GetType() + Environment.NewLine + "Lambda expression should call a single function."); if (!(lambdaExpression.Body is MethodCallExpression methodExpression)) throw new InvalidOperationException("Lambda expression must call a single function."); var TResult = lambdaExpression.ReturnType; if (TResult.IsGenericType && TResult.GetGenericTypeDefinition() == typeof(Task<>)) { // For Task, check if T (i.e. the generic argument) is serializable Type taskTypeArgument = TResult.GetGenericArguments()[0]; if (!IsSerializable(taskTypeArgument)) throw new SerializationException($"The task return type '{taskTypeArgument}' must be added as a JsonSerializable."); } else if (TResult != typeof(Task) && !IsSerializable(TResult)) throw new SerializationException($"Return type '{TResult}' must be added as a JsonSerializable."); var methodInfo = methodExpression.Method; if (methodInfo.MemberType != MemberTypes.Method) throw new InvalidOperationException("Lambda expression must call a single function."); if (!methodInfo.IsStatic) throw new InvalidOperationException("Method must be static."); var internalTargetLevel = ThrowIfUnauthorizedMethodAccess(methodInfo, targetLevel, ApplicationLevel); return new MethodMessage(internalTargetLevel, ApplicationLevel) { Method = new SerializableMethod() { MethodName = methodInfo.Name, ParentClass = new Serializables.SerializableType(methodInfo.ReflectedType), Method = methodInfo, GenericTypes = GetGenericArguments(methodInfo), Parameters = GetParameters(methodExpression.Arguments, lambdaExpression.Parameters, args) }, }; } private class ReplaceVisitor : ExpressionVisitor { private readonly string _oldParamName; private readonly Expression _newParam; public ReplaceVisitor(string oldParamName, Expression newParam) => (_oldParamName, _newParam) = (oldParamName, newParam); protected override Expression VisitParameter(ParameterExpression node) => node.Name == _oldParamName ? _newParam : base.VisitParameter(node); } public static bool IsSerializable(Type type) { return type == typeof(void) || SourceGenerationContext.Default.GetTypeInfo(type) != null; } public static Serializables.SerializableType[] GetGenericArguments(MethodInfo method) { if (!method.IsGenericMethod) return Array.Empty(); return method.GetGenericArguments().Select(arg => new Serializables.SerializableType(arg)).ToArray(); } public static Serializables.SerializableValue[] GetParameters(ReadOnlyCollection arguments, ReadOnlyCollection parameters, (Type Type, object Value)[] args = null) { return arguments.Select(arg => { if (!IsSerializable(arg.Type)) throw new SerializationException($"Argument of type '{arg.Type}' must be added as a JsonSerializable."); Expression expression = arg; if (args != null) { if (parameters.Count != args.Length) throw new SerializationException($"The number of parameters ({parameters.Count}) does not match the number of supplied arguments ({args.Length})."); for (var i = 0; i < parameters.Count; i++) { if (parameters[i].Type != args[i].Type) throw new SerializationException($"The parameter type ({parameters[i].Type}) does not match the argument type ({args[i].Type})."); var argExpression = Expression.Constant(args[i].Value, args[i].Type); expression = new ReplaceVisitor(parameters[i].Name, argExpression).Visit(arg); } } var objectMember = Expression.Convert(expression, typeof(object)); var getterLambda = Expression.Lambda>(objectMember); object value; try { value = getterLambda.Compile().Invoke(); } catch (Exception e) { Log.EnqueueExceptionSafe(e); throw new SerializationException($"Failed to compile parameter of type '{arg.Type}': " + arg.Type + e.Message); } return new Serializables.SerializableValue(arg.Type, value); }).ToArray(); } #endregion #region Internal node controls private static Process LaunchDisposableNode(MethodMessage launchMethod, InternalLevel caller) { var method = launchMethod.Method.ParentClass.Type.GetMethod(launchMethod.Method.MethodName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public, null, launchMethod.Method.Parameters.Select(x => x.Type.Type).ToArray(), null)!; ThrowIfUnauthorizedMethodAccess(method, ApplicationLevel.ToTargetLevel(), caller); var launchCode = (Func)(_ => (int)method.Invoke(null, launchMethod.Method.Parameters.Select(x => x.Value).ToArray())); var processId = launchCode.Invoke(null); return Process.GetProcessById(processId); } private static Process LaunchDisposableNode() { var arguments = $"\"{Directory.GetCurrentDirectory()}\" Interprocess Disposable --Mode TwoWay --Nodes Level={ApplicationLevel}:ProcessID={Process.GetCurrentProcess().Id} --Host {Process.GetCurrentProcess().Id}"; return Process.Start(new ProcessStartInfo(Win32.ProcessEx.GetCurrentProcessFileLocation(), arguments) {UseShellExecute = false, CreateNoWindow = true}); } private static class NodeMethods { [InterprocessMethod(Level.Any)] public static TResult ExecuteDisposable(MethodMessage message, int timeout, bool logExceptions, MethodMessage launchMethod) { using (var mutex = new Mutex(false, "AME-Node-Disposable")) { try { if (!mutex.WaitOne(timeout)) throw new TimeoutException("Disposable mutex timed out. Make sure no other Disposable nodes are running."); var process = launchMethod == null ? LaunchDisposableNode() : LaunchDisposableNode(launchMethod, ApplicationLevel); using var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken(), senderCancel.Token); EventHandler handler = (sender, args) => Wrap.ExecuteSafe(() => { if (process.ExitCode != 0) linkedToken.Cancel(); }); process.EnableRaisingEvents = true; try { process.Exited += handler; if (process.HasExited) linkedToken.Cancel(); message = new MethodMessage(InternalLevel.Disposable, ApplicationLevel) { Method = new SerializableMethod() { MethodName = message.Method.MethodName, GenericTypes = message.Method.GenericTypes, Parameters = message.Method.Parameters, ParentClass = message.Method.ParentClass, }, }; MessageWriteQueue.Add(message); try { if (!message.Processed.Wait(timeout, linkedToken.Token)) message.Result.Exception = new SerializableException(new TimeoutException($"Timeout reached ({timeout}).")); } catch (OperationCanceledException e) { process.EnableRaisingEvents = false; var killException = Wrap.ExecuteSafe(() => { if (!process.HasExited) process.Kill(); }); if (killException != null) Log.EnqueueExceptionSafe(killException); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception ?? senderException ?? (process.HasExited ? new Exception("Disposable node exited unexpectedly with exit code: " + process.ExitCode) : e)); ExceptionDispatchInfo.Capture(message.Result.Exception ?? senderException ?? (process.HasExited ? new Exception("Disposable node exited unexpectedly with exit code: " + process.ExitCode) : e)).Throw(); } if (message.Result.Exception != null) { var killException = Wrap.ExecuteSafe(() => { if (!process.HasExited) process.Kill(); }); if (killException != null) Log.EnqueueExceptionSafe(killException); if (message.Result.Exception.WasDeserialized) message.Result.Exception.Trace.Prepend(new SerializableTrace(null, 0, 5)); if (logExceptions) Log.EnqueueExceptionSafe(message.Result.Exception); ExceptionDispatchInfo.Capture(message.Result.Exception).Throw(); } if (!process.WaitForExit(5000)) throw new TimeoutException("Disposable process took too long to exit."); return typeof(TResult) == typeof(Void) ? default : (TResult)message.Result.Value!.Value; } finally { process.EnableRaisingEvents = false; process.Exited -= handler; process.Dispose(); } } finally { mutex.ReleaseMutex(); } } } } #endregion #region Helper Methods private static void ExitIfHostExited() { if (_hostPID == -1) return; if (Wrap.ExecuteSafe(() => Process.GetProcessById(_hostPID)).Failed) Environment.Exit(-34); } private static byte[] _synchronizationGuid = new Guid("bd5ae0e5-b7ae-4b9a-a14b-ad8539587d2a").ToByteArray(); private class ReadJsonResult { public Guid? MessageID { get; set; } = null; public InternalLevel? Caller { get; set; } = null; [CanBeNull] public byte[] Json { get; set; } = null; [CanBeNull] public Exception Exception { get; set; } = null; } private static ReadJsonResult ReadJson(NamedPipeServerStream pipe, int timeout, CancellationToken token, bool isMessage) { using (_ = new SynchronousIoCanceler(timeout, token)) { byte[] buffer = new byte[2048]; Queue recentBytes = new Queue(); int leftoverIndex = 0; bool guidFound = false; int bytesRead = 0; while (!guidFound) { bytesRead = pipe.Read(buffer, 0, buffer.Length); if (bytesRead == 0) throw new Exception("The end of the stream was reached without finding the target GUID."); for (int i = 0; i < bytesRead; ++i) { if (recentBytes.Count >= 16) recentBytes.Dequeue(); recentBytes.Enqueue(buffer[i]); if (recentBytes.SequenceEqual(_synchronizationGuid)) { leftoverIndex = i + 1; guidFound = true; break; } } } byte[] header = new byte[isMessage ? 24 : 4]; var sizeBytesLeft = header.Length; var bufferSizeLeft = bytesRead - leftoverIndex; if (bufferSizeLeft > 0) { var bytesToCopy = Math.Min(sizeBytesLeft, bufferSizeLeft); Array.Copy(buffer, leftoverIndex, header, 0, bytesToCopy); sizeBytesLeft -= bytesToCopy; leftoverIndex += bytesToCopy; } if (sizeBytesLeft > 0) pipe.Read(header, header.Length - sizeBytesLeft, sizeBytesLeft); var result = new ReadJsonResult(); if (isMessage) { result.MessageID = new Guid(header.Take(16).ToArray()); result.Caller = (InternalLevel)BitConverter.ToInt32(header, 16); } try { int messageLength = BitConverter.ToInt32(header, isMessage ? 20 : 0); if (messageLength > MaxMessageSize) throw new SerializationException($"Received data is more than the maximum message size ({messageLength / 1024}KB > {MaxMessageSize / 1024}KB)."); byte[] jsonBuffer = new byte[messageLength]; var jsonBytesLeft = jsonBuffer.Length; bufferSizeLeft = bytesRead - leftoverIndex; if (bufferSizeLeft > 0) { var bytesToCopy = Math.Min(jsonBytesLeft, bufferSizeLeft); Array.Copy(buffer, leftoverIndex, jsonBuffer, 0, bytesToCopy); jsonBytesLeft -= bytesToCopy; } if (jsonBytesLeft > 0) pipe.Read(jsonBuffer, jsonBuffer.Length - jsonBytesLeft, jsonBytesLeft); result.Json = jsonBuffer; return result; } catch (Exception e) { if (!isMessage) throw; result.Exception = e; return result; } } } private static void WriteJson(NamedPipeClientStream pipe, byte[] utf8JsonBytes, int timeout, CancellationToken token, [CanBeNull] InterMessage message) { byte[] array; if (message != null) array = CombineByteArrays(_synchronizationGuid, message.MessageID.ToByteArray(), BitConverter.GetBytes((int)message.CallerLevel), BitConverter.GetBytes(utf8JsonBytes.Length), utf8JsonBytes); else array = CombineByteArrays(_synchronizationGuid, BitConverter.GetBytes(utf8JsonBytes.Length), utf8JsonBytes); using (_ = new SynchronousIoCanceler(timeout, token)) pipe.Write(array, 0, array.Length); } private static byte[] CombineByteArrays(params byte[][] arrays) { byte[] rv = new byte[arrays.Sum(a => a.Length)]; int offset = 0; foreach (byte[] array in arrays) { Buffer.BlockCopy(array, 0, rv, offset, array.Length); offset += array.Length; } return rv; } private static ConcurrentDictionary _exits = new ConcurrentDictionary(); private static void OnNodeExit(InternalLevel level, int pid, uint exitCode) { bool alreadyExited = false; _exits.AddOrUpdate(level,pid,(_, existingPid) => { alreadyExited = existingPid == pid; return pid; }); if (alreadyExited) return; LevelController.Close(level); var handler = NodeExitedUnexpectedly; if (handler != null) { foreach (var invoker in handler.GetInvocationList()) { SafeTask.Run(() => ((EventHandler)invoker).Invoke(exitCode, level.ToLevel()), true); } } } private static void SetMessageResult(InterMessage message, [CanBeNull] MessageResult result, [CanBeNull] Exception exception) { if (result == null && exception == null) throw new ArgumentNullException(null, "Either value or exception must not be null."); lock (message) { if (!message.Processed.IsSet()) { ResultTasks.TryRemove(message.MessageID, out _); if (message is MethodMessage methodMessage) { var objectException = Wrap.ExecuteSafe(() => { foreach (var argument in methodMessage.Method.Parameters) { if (argument.Value != null && typeof(IInterObject).IsAssignableFrom(argument.Type.Type)) ((IInterObject)argument.Value).OnCompleted(message.TargetLevel); } }); if (objectException != null) Log.EnqueueExceptionSafe(objectException); } if (result != null) message.Result = result; else { if (message.LogExceptions) Log.EnqueueExceptionSafe(exception, null, null, ("Enqueued", "true")); if (exception is SerializableException serializableException) message.Result.Exception = serializableException; else message.Result.Exception = new SerializableException(exception); } message.Processed.Release(); Tasks.TryRemove(message.MessageID, out _); } } } public static bool IsSet(this SemaphoreSlim semaphore) { var result = semaphore.Wait(0); if (result) semaphore.Release(); return result; } [StructLayout(LayoutKind.Sequential)] private struct FileProcessIdsUsingFileInformation { public int NumberOfProcessIdsInList; public IntPtr ProcessIdList; } [DllImport("ntdll.dll")] private static extern uint NtQueryInformationFile( SafeHandle FileHandle, out IntPtr IoStatusBlock, IntPtr FileInformation, uint Length, int FileInformationClass); // Unreliable and slow. Takes at least 10ms on an i7-7700 due to the NtQueryInformationFile call. // The time scales up depending on the number of running processes on the system, probably because // it likely enumerates all processes to find the "locking" process. private static int GetClientPID(NamedPipeServerStream namedPipeServer) { SafePipeHandle handle = namedPipeServer.SafePipeHandle; uint bufferLength = 128; IntPtr bufferPtr = Marshal.AllocHGlobal((int)bufferLength); try { uint status = NtQueryInformationFile(handle, out _, bufferPtr, bufferLength, 47); if (status != 0) throw new Win32Exception((int)status); var result = Marshal.PtrToStructure(bufferPtr); var processIdsListPtr = IntPtr.Add(bufferPtr, Marshal.OffsetOf("ProcessIdList").ToInt32()); List processIds = new List(); for (int i = 0; i < result.NumberOfProcessIdsInList; i++) { int processId = Marshal.ReadInt32(processIdsListPtr, i * sizeof(int)); processIds.Add(processId); } return processIds.First(); } finally { Marshal.FreeHGlobal(bufferPtr); } } [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetNamedPipeServerProcessId(SafePipeHandle Pipe, out uint ServerProcessId); public static void ThrowIfMismatchedServerExePath(NamedPipeClientStream client) { if (!GetNamedPipeServerProcessId(client.SafePipeHandle, out uint id)) throw new Win32Exception(Marshal.GetLastWin32Error()); var currentExe = Win32.ProcessEx.GetCurrentProcessFileLocation(); if (Win32.ProcessEx.GetProcessFileLocation((int)id) != currentExe) throw new SecurityException("Process path mismatch."); } [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetNamedPipeClientProcessId(SafePipeHandle Pipe, out uint ClientProcessId); public static void ThrowIfMismatchedClientExePath(NamedPipeServerStream server) { if (!GetNamedPipeClientProcessId(server.SafePipeHandle, out uint id)) throw new Win32Exception(Marshal.GetLastWin32Error()); var currentExe = Win32.ProcessEx.GetCurrentProcessFileLocation(); if (Win32.ProcessEx.GetProcessFileLocation((int)id) != currentExe) throw new SecurityException("Process path mismatch."); } private static Task ThrowIfUnauthorizedMessageSender(InterMessage message) => ThrowIfUnauthorizedSender(new VerificationRequest() { Type = VerificationType.Message, TargetLevel = message.CallerLevel, CallerLevel = message.TargetLevel, IdToVerify = message.MessageID, JsonHash = message.JsonHash, }); private static Task ThrowIfUnauthorizedResultSender(MessageResult result) => ThrowIfUnauthorizedSender(new VerificationRequest() { Type = VerificationType.Result, TargetLevel = result.MessageTargetLevel, CallerLevel = result.MessageCallerLevel, IdToVerify = result.MessageID, JsonHash = result.JsonHash, }); private static ConcurrentDictionary _verifySchedulerQueue = new ConcurrentDictionary(); private static async Task ThrowIfUnauthorizedSender(VerificationRequest request) { var scheduledAction = (Func)(task => { return Wrap.ExecuteSafe(() => { NamedPipeClientStream clientPipe = new NamedPipeClientStream(".", $"{PipePrefix}-{request.TargetLevel}-VerificationReceiver", PipeDirection.InOut, PipeOptions.None); bool retried = false; byte[] json = JsonSerializer.SerializeToUtf8Bytes(request, _serializerOptions); while (true) { try { clientPipe.ConnectAsync(10000).GetAwaiter().GetResult(); break; } catch (Exception) { if (retried) throw; retried = true; clientPipe.Dispose(); clientPipe = new NamedPipeClientStream(".", $"{PipePrefix}-{request.TargetLevel}-VerificationReceiver", PipeDirection.InOut, PipeOptions.None); } } ThrowIfMismatchedServerExePath(clientPipe); try { WriteJson(clientPipe, json, 5000, CancellationToken.None, null); byte[] verifiedByte = new byte[] { 0 }; using (_ = new SynchronousIoCanceler(5000)) clientPipe.Read(verifiedByte, 0, verifiedByte.Length); if (verifiedByte[0] != 1) throw new UnauthorizedAccessException("Verification failed."); } catch (OperationCanceledException) { throw new TimeoutException("Verification request timed out."); } finally { clientPipe.Close(); } }); }); var task = _verifySchedulerQueue.GetOrAdd(request.TargetLevel, _ => (Task.CompletedTask, new object())); Task toBeAwaited; lock (task.Lock) { task.Task = toBeAwaited = task.Task.ContinueWith(scheduledAction); } var exception = await toBeAwaited; if (exception!= null) ExceptionDispatchInfo.Capture(exception).Throw(); } private static InternalLevel ThrowIfUnauthorizedMethodAccess(MethodInfo method, TargetLevel targetLevel, InternalLevel callerLevel) { var attribute = method.GetCustomAttribute(); if (attribute == null) throw new UnauthorizedAccessException($"Method '{method.Name}' must have the InterprocessMethod attribute."); if (targetLevel == TargetLevel.Auto && (attribute.AuthorizedExecutors.Length > 1 || attribute.AuthorizedExecutors[0] == Level.Any)) throw new ArgumentException("A specific target level must be specified when multiple InterprocessMethod authorized executors are defined.", nameof(targetLevel)); if (targetLevel != TargetLevel.Auto && (ApplicationLevel == InternalLevel.Disposable ? (!attribute.AuthorizedExecutors.Contains(targetLevel.ToLevel()) && !attribute.AuthorizedExecutors.Contains(Level.Disposable)) : !attribute.AuthorizedExecutors.Contains(targetLevel.ToLevel())) && !attribute.AuthorizedExecutors.Contains(Level.Any)) throw new UnauthorizedAccessException($"Access to method '{method.Name}' is denied from target level {targetLevel}."); if (!attribute.AuthorizedCallers.Contains(callerLevel.ToLevel()) && !attribute.AuthorizedCallers.Contains(Level.Any)) throw new UnauthorizedAccessException($"Access to method '{method.Name}' is denied from caller level {targetLevel}."); return targetLevel == TargetLevel.Auto ? attribute.AuthorizedExecutors[0].ToInternalLevel() : targetLevel.ToInternalLevel(); } private static void CancelReceiver() { if (receiverCancel.IsCancellationRequested) return; receiverCancel.Cancel(); } private static void CancelReceiver(Exception exception, bool logException) { if (receiverCancel.IsCancellationRequested) return; receiverException = exception; receiverCancel.Cancel(); if (logException) Log.WriteExceptionSafe(exception); } private static void CancelSender() { if (senderCancel.IsCancellationRequested) return; senderCancel.Cancel(); } private static void CancelSender(Exception exception, bool logException) { if (senderCancel.IsCancellationRequested) return; senderException = exception; senderCancel.Cancel(); if (logException) Log.WriteExceptionSafe(exception); } #region Interfaces internal interface IInterObject { public void BeforeSend(InternalLevel targetLevel); public void OnCompleted(InternalLevel targetLevel); } #endregion #endregion } #region Attributes public class InterprocessMethodAttribute : Attribute { [NotNull] public Level[] AuthorizedExecutors { get; set; } [NotNull] public Level[] AuthorizedCallers { get; set; } public InterprocessMethodAttribute([NotNull] Level[] authorizedExecutors) : this(authorizedExecutors, new[] { Level.Any }) { } public InterprocessMethodAttribute(Level authorizedExecutor) : this(new[] { authorizedExecutor }, Level.Any) { } public InterprocessMethodAttribute(Level authorizedExecutor, Level authorizedCaller) : this(new[] { authorizedExecutor }, new[] { authorizedCaller }) { } public InterprocessMethodAttribute([NotNull] Level[] authorizedExecutors, Level authorizedCaller) : this(authorizedExecutors, new[] { authorizedCaller }) { } public InterprocessMethodAttribute(Level authorizedExecutor, [NotNull] Level[] authorizedCallers) : this(new[] { authorizedExecutor }, authorizedCallers) { } public InterprocessMethodAttribute([NotNull] Level[] authorizedExecutors, [NotNull] Level[] authorizedCallers) { if (authorizedExecutors == null) throw new ArgumentNullException(nameof(authorizedExecutors)); if (authorizedCallers == null) throw new ArgumentNullException(nameof(authorizedCallers)); if (authorizedExecutors.Length == 0) throw new ArgumentException("At least one authorized executor level must be specified."); if (authorizedCallers.Length == 0) throw new ArgumentException("At least one authorized caller level must be specified."); AuthorizedExecutors = authorizedExecutors; AuthorizedCallers = authorizedCallers; } } #endregion } ================================================ FILE: Interprocess/Interprocess.projitems ================================================  $(MSBuildAllProjects);$(MSBuildThisFileFullPath) true 721ADC75-0B17-43D4-A5B2-1C0C58801B53 Interprocess ================================================ FILE: Interprocess/Interprocess.shproj ================================================ {64B44592-888F-4C21-97EC-5DD7D84ED300} v4.7.2 ================================================ FILE: Interprocess/JsonSerializables.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text.Json.Serialization; using Core.Miscellaneous; using JetBrains.Annotations; using Core; using iso_mode; using TrustedUninstaller.Shared; namespace Interprocess { public partial class InterLink { [JsonSerializable(typeof(Playbook))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(System.IO.DriveInfo))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(USB.UsbDisk))] [JsonSerializable(typeof(USB.UsbDevice))] // Custom types [JsonSerializable(typeof(InterMessage))] [JsonSerializable(typeof(InterProgress))] [JsonSerializable(typeof(InterMessageReporter))] [JsonSerializable(typeof(InterCancellationTokenSource))] [JsonSerializable(typeof(Serializables.SerializableType))] [JsonSerializable(typeof(Serializables.SerializableValue))] [JsonSerializable(typeof(MethodMessage))] [JsonSerializable(typeof(TextMessage))] [JsonSerializable(typeof(ProgressMessage))] [JsonSerializable(typeof(MessageReportMessage))] [JsonSerializable(typeof(NodeRegistrationMessage))] [JsonSerializable(typeof(TokenCancellationMessage))] [JsonSerializable(typeof(ShutdownMessage))] [JsonSerializable(typeof(SerializableMethod))] [JsonSerializable(typeof(MessageResult))] [JsonSerializable(typeof(SerializableException))] [JsonSerializable(typeof(SerializableTrace))] [JsonSerializable(typeof(Void))] // Primitive types [JsonSerializable(typeof(byte))] [JsonSerializable(typeof(sbyte))] [JsonSerializable(typeof(int))] [JsonSerializable(typeof(uint))] [JsonSerializable(typeof(short))] [JsonSerializable(typeof(ushort))] [JsonSerializable(typeof(long))] [JsonSerializable(typeof(ulong))] [JsonSerializable(typeof(float))] [JsonSerializable(typeof(double))] [JsonSerializable(typeof(decimal))] [JsonSerializable(typeof(bool))] [JsonSerializable(typeof(byte?))] [JsonSerializable(typeof(sbyte?))] [JsonSerializable(typeof(int?))] [JsonSerializable(typeof(uint?))] [JsonSerializable(typeof(short?))] [JsonSerializable(typeof(ushort?))] [JsonSerializable(typeof(long?))] [JsonSerializable(typeof(ulong?))] [JsonSerializable(typeof(float?))] [JsonSerializable(typeof(double?))] [JsonSerializable(typeof(decimal?))] [JsonSerializable(typeof(bool?))] // Additional types [JsonSerializable(typeof(string))] [JsonSerializable(typeof(Enum))] [JsonSerializable(typeof(DateTime))] [JsonSerializable(typeof(DateTimeOffset))] [JsonSerializable(typeof(Guid))] [JsonSerializable(typeof(DateTime?))] [JsonSerializable(typeof(DateTimeOffset?))] [JsonSerializable(typeof(Guid?))] [JsonSerializable(typeof(Architecture))] [JsonSerializable(typeof(Architecture?))] [JsonSerializable(typeof(Uri))] // TODO: On switch to .NET 8 with trimming internal partial class SourceGenerationContext // : JsonSerializerContext { } { public static SourceGenerationContext Default { get; } = new SourceGenerationContext(); public static HashSet SerializableTypes { get; } = new HashSet { typeof(List), typeof(USB.UsbDisk), typeof(USB.UsbDevice), typeof(System.IO.DriveInfo), typeof(Playbook), // Custom types typeof(InterMessage), typeof(InterProgress), typeof(InterMessageReporter), typeof(InterCancellationTokenSource), typeof(Serializables.SerializableType), typeof(Serializables.SerializableValue), typeof(MethodMessage), typeof(TextMessage), typeof(ProgressMessage), typeof(MessageReportMessage), typeof(NodeRegistrationMessage), typeof(TokenCancellationMessage), typeof(ShutdownMessage), typeof(SerializableMethod), typeof(MessageResult), typeof(SerializableException), typeof(SerializableTrace), typeof(Void), // Primitive types typeof(byte), typeof(sbyte), typeof(int), typeof(uint), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal), typeof(bool), typeof(byte?), typeof(sbyte?), typeof(int?), typeof(uint?), typeof(short?), typeof(ushort?), typeof(long?), typeof(ulong?), typeof(float?), typeof(double?), typeof(decimal?), typeof(bool?), // Additional types typeof(string), typeof(Enum), typeof(DateTime), typeof(DateTimeOffset), typeof(Guid), typeof(DateTime?), typeof(DateTimeOffset?), typeof(Guid?), typeof(Architecture), typeof(Architecture?), typeof(Uri) }; [CanBeNull] public object GetTypeInfo(Type type) { if (SerializableTypes.Contains(type) || (type.IsArray && SerializableTypes.Contains(type.GetElementType())) || type.IsEnum) return new object(); return null; } } } } ================================================ FILE: Interprocess/LevelController.cs ================================================ using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Runtime.ExceptionServices; using System.Security; using System.Threading; using System.Threading.Tasks; using System.Windows; using JetBrains.Annotations; using ThreadState = System.Diagnostics.ThreadState; using Core; namespace Interprocess { public partial class InterLink { private static class LevelController { #region Registration private static readonly ConcurrentDictionary LaunchCode, Mode StartingMode, int HostPID)> _registrations = new ConcurrentDictionary LaunchCode, Mode StartingMode, int HostPID)>(); public static void Register(InternalLevel level, int processID, [CanBeNull] Func launchCode = null, Mode startingMode = Mode.TwoWay, int hostPid = -1) { _registrations.AddOrUpdate(level, _ => (processID, launchCode, startingMode, hostPid), (_, existing) => (processID, launchCode, startingMode, hostPid)); Open(level); } public static void Unregister(InternalLevel level) { _registrations.TryRemove(level, out _); Close(level); } public static (InternalLevel Level, int ProcessID)[] GetRegisteredNodes() => _registrations.Select(x => (x.Key, x.Value.ProcessID)).ToArray(); #endregion #region Open/close controls private static readonly ConcurrentDictionary _closeTokens = new ConcurrentDictionary(); [NotNull] public static void Open(InternalLevel level) { _closeTokens.AddOrUpdate( level, _ => (new object(), new CancellationTokenSource()), (_, oldTuple) => { lock (oldTuple.Lock) { oldTuple.CancellationToken.Dispose(); return (new object(), new CancellationTokenSource()); } }); } [NotNull] public static CancellationToken GetCloseToken(InternalLevel level) { var tuple = _closeTokens.GetOrAdd(level, _ => { var cts = new CancellationTokenSource(); cts.Cancel(); return (new object(), cts); }); lock (tuple.Lock) return tuple.CancellationToken.Token; } public static void Close(InternalLevel level) { var tuple = _closeTokens.GetOrAdd(level, _ => (new object(), new CancellationTokenSource())); lock (tuple.Lock) tuple.CancellationToken.Cancel(); } [NotNull] public static bool IsClosed(InternalLevel level) { var tuple = _closeTokens.GetOrAdd(level, _ => { var cts = new CancellationTokenSource(); cts.Cancel(); return (new object(), cts); }); lock (tuple.Lock) return tuple.CancellationToken.IsCancellationRequested; } private static readonly object _nodeCheckLock = new object(); public static void ThrowIfClosedOrExited(InternalLevel level, bool relaunchIfExited) { if (!_registrations.TryGetValue(level, out var registrationInfo)) throw new SecurityException($"Node of level '{level}' has not been registered."); lock (_nodeCheckLock) { if (IsClosed(level)) throw new OperationCanceledException($"InternalLevel {level} is closed."); using var handle = Win32.Process.OpenProcess(Win32.Process.ProcessAccessFlags.QueryLimitedInformation, false, registrationInfo.ProcessID); bool hasExited = handle.IsInvalid; if (hasExited) { if (registrationInfo.LaunchCode == null || !relaunchIfExited || Tasks.Values.Count(x => x.TargetLevel == level) > 1) { foreach (var message in Tasks.Values.Where(x => x.TargetLevel == level)) SetMessageResult(message, null, senderException ?? new ApplicationException($"Node '{level}' exited unexpectedly.")); OnNodeExit(level, registrationInfo.ProcessID, 2004); throw new SecurityException($"Node of level '{level}' has exited."); } try { _registrations.TryRemove(level, out _); LaunchNode(registrationInfo.LaunchCode, level.ToLevel(), registrationInfo.StartingMode, registrationInfo.HostPID, true); if (!_registrations.TryGetValue(level, out registrationInfo)) throw new SecurityException($"Node level '{level}' relaunch re-registration failed."); } catch (Exception e) { foreach (var message in Tasks.Values.Where(x => x.TargetLevel == level)) SetMessageResult(message, null, senderException ?? e); Log.EnqueueExceptionSafe(e); OnNodeExit(level, registrationInfo.ProcessID, 2003); throw; } } var currentExe = Win32.ProcessEx.GetCurrentProcessFileLocation(); var levelExe = Win32.ProcessEx.GetProcessFileLocation(registrationInfo.ProcessID); if (levelExe != currentExe) { Log.EnqueueSafe(LogType.Error, "Path mismatch.", new SerializableTrace(), null, ($"{level} Path: ", levelExe), ($"Current Path: ", currentExe)); foreach (var message in Tasks.Values.Where(x => x.TargetLevel == level)) SetMessageResult(message, null, senderException ?? new ApplicationException($"Node '{level}' path mismatch.")); OnNodeExit(level, registrationInfo.ProcessID, 2002); throw new SecurityException("Process path mismatch."); } } } #endregion } } } ================================================ FILE: Interprocess/Levels.cs ================================================ using System; namespace Interprocess { public enum Level { Any, Disposable, // Add/remove level here User, Administrator, TrustedInstaller, } public enum TargetLevel { Auto, Disposable, // And add/remove level here User, Administrator, TrustedInstaller, } public partial class InterLink { public enum InternalLevel { Uninitialized, Disposable, // And add/remove level here User, Administrator, TrustedInstaller, } public static InternalLevel ToInternalLevel(this Level level) { return level switch { // And add/remove conversion line here Level.User => InternalLevel.User, Level.Administrator => InternalLevel.Administrator, Level.TrustedInstaller => InternalLevel.TrustedInstaller, Level.Disposable => InternalLevel.Disposable, Level.Any => throw new InvalidOperationException("Invalid conversion of Level.Any to InternalLevel."), _ => throw new InvalidOperationException() }; } public static InternalLevel ToInternalLevel(this TargetLevel level) { return level switch { // And add/remove conversion line here TargetLevel.User => InternalLevel.User, TargetLevel.Administrator => InternalLevel.Administrator, TargetLevel.TrustedInstaller => InternalLevel.TrustedInstaller, TargetLevel.Disposable => InternalLevel.Disposable, TargetLevel.Auto => throw new InvalidOperationException("Invalid conversion of TargetLevel.Auto to InternalLevel."), _ => throw new InvalidOperationException() }; } public static Level ToLevel(this TargetLevel level) { return level switch { // And add/remove conversion line here TargetLevel.User => Level.User, TargetLevel.Administrator => Level.Administrator, TargetLevel.TrustedInstaller => Level.TrustedInstaller, TargetLevel.Disposable => Level.Disposable, TargetLevel.Auto => throw new InvalidOperationException("Invalid conversion of TargetLevel.Auto to Level."), _ => throw new InvalidOperationException() }; } public static Level ToLevel(this InternalLevel level) { return level switch { // And add/remove conversion line here InternalLevel.User => Level.User, InternalLevel.Administrator => Level.Administrator, InternalLevel.TrustedInstaller => Level.TrustedInstaller, InternalLevel.Disposable => Level.Disposable, InternalLevel.Uninitialized => throw new InvalidOperationException("Invalid conversion of InternalLevel.Uninitialized to Level."), _ => throw new InvalidOperationException() }; } public static TargetLevel ToTargetLevel(this InternalLevel level) { return level switch { // And add/remove conversion line here InternalLevel.User => TargetLevel.User, InternalLevel.Administrator => TargetLevel.Administrator, InternalLevel.TrustedInstaller => TargetLevel.TrustedInstaller, InternalLevel.Disposable => TargetLevel.Disposable, InternalLevel.Uninitialized => throw new InvalidOperationException("Invalid conversion of InternalLevel.Uninitialized to TargetLevel."), _ => throw new InvalidOperationException() }; } public static TargetLevel ToTargetLevel(this Level level) { return level switch { // And add/remove conversion line here Level.User => TargetLevel.User, Level.Administrator => TargetLevel.Administrator, Level.TrustedInstaller => TargetLevel.TrustedInstaller, Level.Disposable => TargetLevel.Disposable, Level.Any => throw new InvalidOperationException("Invalid conversion of Level.Any to TargetLevel."), _ => throw new InvalidOperationException() }; } } } ================================================ FILE: Interprocess/Serialization.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO.Pipes; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Metadata; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using System.Windows; using Core.Miscellaneous; using JetBrains.Annotations; using Core; using Expression = System.Linq.Expressions.Expression; namespace Interprocess { public static partial class InterLink { #region Serialzier internal static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions() { Converters = { new SerializableExceptionConverter(), new SerializableValueConverter(), new SerializableTypeConverter(), } }; #region Converters public class SerializableExceptionConverter : JsonConverter { public override SerializableException Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException(); } var serializableException = new SerializableException(null); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) { serializableException.OnDeserialized(); return serializableException; } if (reader.TokenType == JsonTokenType.PropertyName) { var propertyName = reader.GetString(); reader.Read(); switch (propertyName) { case "Trace": serializableException.Trace = JsonSerializer.Deserialize(ref reader, options); break; case "OriginalTraceString": serializableException.OriginalTraceString = reader.GetString(); break; case "OriginalType": serializableException.OriginalType = JsonSerializer.Deserialize(ref reader, options); break; case "Message": serializableException.Message = reader.GetString(); break; case "InnerException": serializableException.InnerException = reader.TokenType == JsonTokenType.Null ? null : JsonSerializer.Deserialize(ref reader, options); break; case "AggregateInnerExceptions": serializableException.AggregateInnerExceptions = reader.TokenType == JsonTokenType.Null ? null : JsonSerializer.Deserialize(ref reader, options); break; } } } throw new JsonException(); } public override void Write(Utf8JsonWriter writer, SerializableException value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WritePropertyName("Trace"); JsonSerializer.Serialize(writer, value.Trace, options); writer.WritePropertyName("OriginalTraceString"); writer.WriteStringValue(value.OriginalTraceString); writer.WritePropertyName("OriginalType"); JsonSerializer.Serialize(writer, value.OriginalType, options); writer.WritePropertyName("Message"); writer.WriteStringValue(value.Message); writer.WritePropertyName("InnerException"); JsonSerializer.Serialize(writer, value.InnerException, options); writer.WritePropertyName("AggregateInnerExceptions"); JsonSerializer.Serialize(writer, value.AggregateInnerExceptions, options); writer.WriteEndObject(); } } private class SerializableTypeConverter : JsonConverter { public override Serializables.SerializableType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException(); } Type type = null; string typeName; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) return new Serializables.SerializableType(type); if (reader.TokenType == JsonTokenType.PropertyName) { string propertyName = reader.GetString(); reader.Read(); if (propertyName == "TypeName") { typeName = reader.GetString(); type = Type.GetType(typeName!); } } } throw new JsonException(); } public override void Write(Utf8JsonWriter writer, Serializables.SerializableType value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString("TypeName", value.TypeName); writer.WriteEndObject(); } } private class SerializableValueConverter : JsonConverter { public override Serializables.SerializableValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException(); } Type type = null; object value = null; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) return new Serializables.SerializableValue(type, value); if (reader.TokenType == JsonTokenType.PropertyName) { string propertyName = reader.GetString(); reader.Read(); if (propertyName == "Type") { var serializableType = JsonSerializer.Deserialize(ref reader, options); type = serializableType.Type; } else if (propertyName == "Value") { if (type != null) { value = JsonSerializer.Deserialize(ref reader, type, options); } } } } throw new JsonException(); } public override void Write(Utf8JsonWriter writer, Serializables.SerializableValue value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WritePropertyName("Type"); JsonSerializer.Serialize(writer, value.Type, options); if (value.Type.Type != typeof(void)) { writer.WritePropertyName("Value"); JsonSerializer.Serialize(writer, value.Value, value.Type.Type, options); } writer.WriteEndObject(); } } #endregion #endregion #region Definitions #region Public [Serializable] public class InterCancellationTokenSource : IInterObject, IDisposable, IJsonOnDeserialized { public InternalLevel SourceLevel { get; set; } = ApplicationLevel; public Guid ID { get; set; } = Guid.NewGuid(); public bool IsCancellationRequested { get; set; } = false; public InterCancellationTokenSource() : this(new CancellationTokenSource()) {} public InterCancellationTokenSource(CancellationTokenSource underlyingToken) { _underlyingToken = underlyingToken; if (SourceLevel == ApplicationLevel) TokenTasks[ID] = this; } #region NonSerialized Logic private CancellationTokenSource _underlyingToken; private List _targets = new List(); [JsonIgnore] public CancellationToken Token => _underlyingToken.Token; private object _cancellationLock = new object(); public void Cancel() => Cancel(ApplicationLevel); internal void Cancel(InternalLevel cancelSource) { lock (_cancellationLock) { if (IsCancellationRequested) return; IsCancellationRequested = true; } var localCancelException = Wrap.ExecuteSafe(() => _underlyingToken.Cancel()); var interCancelException = Wrap.ExecuteSafe(() => { if (SourceLevel != ApplicationLevel && _wasDeserialized) { MessageWriteQueue.Add(new TokenCancellationMessage(SourceLevel, ApplicationLevel) { TokenID = ID, SourceLevel = SourceLevel, }); } else { foreach (var target in _targets.Where(target => target != cancelSource)) { MessageWriteQueue.Add(new TokenCancellationMessage(target, ApplicationLevel) { TokenID = ID, SourceLevel = SourceLevel, }); } } }); if (localCancelException != null || interCancelException != null) ExceptionDispatchInfo.Capture(localCancelException ?? interCancelException).Throw(); } public void BeforeSend(InternalLevel targetLevel) { if (targetLevel == SourceLevel) throw new InvalidOperationException($"Target level ({targetLevel.ToString()}) for an InterCancellationTokenSource must not be the same as the source level ({SourceLevel})."); _targets.Add(targetLevel); } public void OnCompleted(InternalLevel targetLevel) => _targets.Remove(targetLevel); private bool _wasDeserialized = false; public void OnDeserialized() { if (IsCancellationRequested) _underlyingToken.Cancel(); _wasDeserialized = true; } public void Dispose() { if (!_wasDeserialized) TokenTasks.TryRemove(ID, out _); _underlyingToken.Dispose(); } #endregion } [Serializable] public class InterProgress : Progress, IDisposable, IProgress { public Guid ID { get; private set; } public InternalLevel Receiver { get; set; } public decimal Maximum { get; set; } = 100m; [JsonConstructor] public InterProgress(Guid id, InternalLevel receiver) { ID = id; Receiver = receiver; } public InterProgress(Action handler) : base(handler) { Receiver = ApplicationLevel; ID = Guid.NewGuid(); ProgressTasks[ID] = this; } #region NonSerialized Logic public void Report(decimal value) { if (value < 0m) throw new InvalidOperationException("Reported progress must not be less than zero"); if (ApplicationLevel == Receiver) OnProgressReceived(value); else SendProgress(value); } public void OnProgressReceived(decimal progress) { if (ApplicationLevel != Receiver) throw new InvalidOperationException($"OnProgressReceived must be called from the receiving node ({Receiver})."); if (progress < 0m || progress > Maximum) return; base.OnReport(progress); } private void SendProgress(decimal value) { var interMessage = new ProgressMessage(Receiver, ApplicationLevel) { ProgressID = ID, Value = value, }; MessageWriteQueue.Add(interMessage); } public new void OnReport(decimal progress) => throw new NotImplementedException(); public void Dispose() { if (ApplicationLevel == Receiver) { ProgressTasks.TryRemove(ID, out _); } } #endregion } [Serializable] public class InterMessageReporter : Progress, IDisposable, IProgress { public Guid ID { get; private set; } public InternalLevel Receiver { get; set; } [JsonConstructor] public InterMessageReporter(Guid id, InternalLevel receiver) { ID = id; Receiver = receiver; } public InterMessageReporter(Action handler) : base(handler) { Receiver = ApplicationLevel; ID = Guid.NewGuid(); MessageReportTasks[ID] = this; } #region NonSerialized Logic public void Report(string value) { if (ApplicationLevel == Receiver) OnMessageReceived(value); else SendProgress(value); } public void OnMessageReceived(string progress) { if (ApplicationLevel != Receiver) throw new InvalidOperationException($"OnProgressReceived must be called from the receiving node ({Receiver})."); base.OnReport(progress); } private void SendProgress(string value) { var interMessage = new MessageReportMessage(Receiver, ApplicationLevel) { ReporterID = ID, Value = value, }; MessageWriteQueue.Add(interMessage); } public new void OnReport(string progress) => throw new NotImplementedException(); public void Dispose() { if (ApplicationLevel == Receiver) { MessageReportTasks.TryRemove(ID, out _); } } #endregion } #endregion #region Private [JsonDerivedType(typeof(MethodMessage), 0)] [JsonDerivedType(typeof(TextMessage), 1)] [JsonDerivedType(typeof(ProgressMessage), 2)] [JsonDerivedType(typeof(MessageReportMessage), 3)] [JsonDerivedType(typeof(TokenCancellationMessage), 4)] [JsonDerivedType(typeof(NodeRegistrationMessage), 5)] [JsonDerivedType(typeof(ShutdownMessage), 6)] private class InterMessage { [JsonConstructor] public InterMessage(Guid messageId) => MessageID = messageId; protected InterMessage() { } protected InterMessage(InternalLevel targetLevel, InternalLevel callerLevel) { TargetLevel = targetLevel; CallerLevel = callerLevel; } public Guid MessageID { get; set; } public InternalLevel TargetLevel { get; set; } public InternalLevel CallerLevel { get; set; } [NotNull] public MessageResult Result { get; set; } = MessageResult.Empty; [NonSerialized] internal readonly SemaphoreSlim Processed = new SemaphoreSlim(0, 1); [NonSerialized] internal byte[] JsonHash = {}; [NonSerialized] internal bool PendingVerification = false; [NonSerialized] internal bool Enqueued = false; [NonSerialized] internal int EnqueueTimeout; [NonSerialized] internal bool Sent = false; [NonSerialized] internal bool LogExceptions = false; } private class MethodMessage : InterMessage { [JsonConstructor] public MethodMessage(Guid messageId) => MessageID = messageId; public MethodMessage(InternalLevel targetLevel, InternalLevel callerLevel) : base(targetLevel, callerLevel) { } public SerializableMethod Method { get; set; } = null; } private class TextMessage : InterMessage { [JsonConstructor] public TextMessage(Guid messageId) => MessageID = messageId; public TextMessage(InternalLevel targetLevel, InternalLevel callerLevel) : base(targetLevel, callerLevel) { } public string Text { get; set; } = null; } private class ProgressMessage : InterMessage { [JsonConstructor] public ProgressMessage(Guid messageId) => MessageID = messageId; public ProgressMessage(InternalLevel targetLevel, InternalLevel callerLevel) : base(targetLevel, callerLevel) { } public Guid ProgressID { get; set; } public decimal Value { get; set; } } private class MessageReportMessage : InterMessage { [JsonConstructor] public MessageReportMessage(Guid messageId) => MessageID = messageId; public MessageReportMessage(InternalLevel targetLevel, InternalLevel callerLevel) : base(targetLevel, callerLevel) { } public Guid ReporterID { get; set; } public string Value { get; set; } } private class TokenCancellationMessage : InterMessage { [JsonConstructor] public TokenCancellationMessage(Guid messageId) => MessageID = messageId; public TokenCancellationMessage(InternalLevel targetLevel, InternalLevel callerLevel) : base(targetLevel, callerLevel) { } public Guid TokenID { get; set; } public InternalLevel SourceLevel { get; set; } } private class NodeRegistrationMessage : InterMessage { [JsonConstructor] public NodeRegistrationMessage(Guid messageId) => MessageID = messageId; public NodeRegistrationMessage(InternalLevel targetLevel, InternalLevel callerLevel) : base(targetLevel, callerLevel) { } public InternalLevel Level { get; set; } public int ProcessID { get; set; } } private class ShutdownMessage : InterMessage { [JsonConstructor] public ShutdownMessage(Guid messageId) => MessageID = messageId; public ShutdownMessage(InternalLevel targetLevel, InternalLevel callerLevel) : base(targetLevel, callerLevel) { } } [Serializable] public class SerializableMethod { public Serializables.SerializableType ParentClass { get; set; } public string MethodName { get; set; } public Serializables.SerializableValue[] Parameters { get; set; } = Array.Empty(); public Serializables.SerializableType[] GenericTypes { get; set; } = Array.Empty(); [NonSerialized] internal MethodInfo Method; } [Serializable] private class MessageResult { public static readonly MessageResult Empty = new MessageResult(); public Guid MessageID { get; set; } public InternalLevel MessageCallerLevel { get; set; } public InternalLevel MessageTargetLevel { get; set; } private MessageResult() { } [JsonConstructor] public MessageResult(Guid messageId) => MessageID = messageId; public MessageResult(Guid messageId, InternalLevel messageTargetLevel, InternalLevel messageCallerLevel, Serializables.SerializableValue value) { MessageID = messageId; MessageTargetLevel = messageTargetLevel; MessageCallerLevel = messageCallerLevel; Value = value; Exception = null; } public MessageResult(Guid messageId, InternalLevel messageTargetLevel, InternalLevel messageCallerLevel, SerializableException exception) { MessageID = messageId; MessageTargetLevel = messageTargetLevel; MessageCallerLevel = messageCallerLevel; Value = null; Exception = exception; } [CanBeNull] public Serializables.SerializableValue Value { get; set; } = null; [CanBeNull] public SerializableException Exception { get; set; } = null; [NonSerialized] internal byte[] JsonHash = null; [NonSerialized] internal bool PendingVerification = false; } public enum VerificationType { Message, Result, } public class VerificationRequest { public VerificationType Type { get; set; } public Guid IdToVerify { get; set; } public byte[] JsonHash { get; set; } public InternalLevel CallerLevel { get; set; } public InternalLevel TargetLevel { get; set; } } #endregion #endregion } } ================================================ FILE: Interprocess/SynchronousIOCanceler.cs ================================================ using System; using System.Runtime.InteropServices; using System.Threading; namespace Interprocess { public class SynchronousIoCanceler : IDisposable { [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CancelSynchronousIo(IntPtr hThread); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr hObject); [Flags] public enum ThreadAccess { Terminate = 0x0001, SuspendResume = 0x0002, GetContext = 0x0008, SetContext = 0x0010, SetInformation = 0x0020, QueryInformation = 0x0040, SetThreadToken = 0x0080, Impersonate = 0x0100, DirectImpersonation = 0x0200, SetLimitedInformation = 0x0400, QueryLimitedInformation = 0x0800 } private readonly IntPtr _thread; private Timer _cancelLoop; private CancellationTokenRegistration _registration; public SynchronousIoCanceler(CancellationToken token, int cancelInterval = 1000) { _thread = OpenThread(ThreadAccess.Terminate, false, GetCurrentThreadId()); _registration = token.Register(() => _cancelLoop = new Timer(_ => { CancelSynchronousIo(_thread); }, null, 0, cancelInterval)); } public SynchronousIoCanceler(int timeout, int cancelInterval = 1000) { _thread = OpenThread(ThreadAccess.Terminate, false, GetCurrentThreadId()); _cancelLoop = new Timer(_ => { CancelSynchronousIo(_thread); }, null, timeout, cancelInterval); } public SynchronousIoCanceler(int timeout, CancellationToken token, int cancelInterval = 1000) { if (token == CancellationToken.None) { _thread = OpenThread(ThreadAccess.Terminate, false, GetCurrentThreadId()); _cancelLoop = new Timer(_ => { CancelSynchronousIo(_thread); }, null, timeout, cancelInterval); return; } _thread = OpenThread(ThreadAccess.Terminate, false, GetCurrentThreadId()); _cancelLoop = new Timer(_ => { CancelSynchronousIo(_thread); }, null, token.IsCancellationRequested ? 0 : timeout, cancelInterval); if (!token.IsCancellationRequested) _registration = token.Register(() => _cancelLoop.Change(0, cancelInterval)); } public void Dispose() { _registration.Dispose(); _cancelLoop?.Dispose(); if (_thread != IntPtr.Zero) CloseHandle(_thread); } } } ================================================ FILE: Interprocess/Threads.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Windows; using ThreadState = System.Diagnostics.ThreadState; using Core; using Core.Miscellaneous; namespace Interprocess { public partial class InterLink { #region Input private static Thread receiverThread = null; private static Thread sendResultThread = null; private static Exception receiverException = null; private static readonly CancellationTokenSource receiverCancel = new CancellationTokenSource(); private static readonly BlockingCollection ResultWriteQueue = new BlockingCollection(); private static readonly ConcurrentDictionary Tasks = new ConcurrentDictionary(); private static readonly ConcurrentDictionary ProgressTasks = new ConcurrentDictionary(); private static readonly ConcurrentDictionary MessageReportTasks = new ConcurrentDictionary(); private static readonly ConcurrentDictionary ResultTasks = new ConcurrentDictionary(); private static readonly ConcurrentDictionary TokenTasks = new ConcurrentDictionary(); private static readonly ConcurrentDictionary> ActiveTokens = new ConcurrentDictionary>(); private static void ThrowIfReceiverViolation(InternalLevel level, bool relaunchIfExited) { receiverCancel.Token.ThrowIfCancellationRequested(); LevelController.ThrowIfClosedOrExited(level, relaunchIfExited); if (_mode == Mode.SendOnly) throw new InvalidOperationException($"An attempt was made to send a message to a send-only level '{level}'."); } private static void ReceiverThread(PipeSecurity security) { var exception = Wrap.ExecuteSafe(() => { var serverPipe = new NamedPipeServerStream($"{PipePrefix}-{ApplicationLevel}-Receiver", PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, security); try { while (!receiverCancel.IsCancellationRequested) { try { serverPipe.WaitForConnectionAsync(receiverCancel.Token).GetAwaiter().GetResult(); } catch (IOException) { serverPipe.Dispose(); serverPipe = new NamedPipeServerStream($"{PipePrefix}-{ApplicationLevel}-Receiver", PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, security); continue; } ReadJsonResult jsonResult = null; try { ThrowIfMismatchedClientExePath(serverPipe); try { jsonResult = ReadJson(serverPipe, 2500, CancellationToken.None, true); if (jsonResult.Exception != null || jsonResult.Json == null) ExceptionDispatchInfo.Capture(jsonResult.Exception ?? new Exception("Failed to read json.")).Throw(); } catch (OperationCanceledException) { throw new TimeoutException("Deserialization took too long."); } } catch (Exception e) { Wrap.ExecuteSafe(() => serverPipe.Disconnect()); if (jsonResult != null && jsonResult.MessageID.HasValue && jsonResult.Caller.HasValue) ResultWriteQueue.Add(new MessageResult(jsonResult.MessageID.Value, ApplicationLevel, jsonResult.Caller.Value, new SerializableException(e))); Log.EnqueueExceptionSafe(e); continue; } Wrap.ExecuteSafe(() => serverPipe.Disconnect()); Task.Run(async () => { InterMessage message = null; try { message = JsonSerializer.Deserialize(jsonResult.Json, _serializerOptions); using (MD5 md5 = new MD5CryptoServiceProvider()) message.JsonHash = md5.ComputeHash(jsonResult.Json); if (!(message is ProgressMessage)) { if (!(message is NodeRegistrationMessage)) ThrowIfReceiverViolation(message.CallerLevel, false); await ThrowIfUnauthorizedMessageSender(message); ExitIfHostExited(); } switch (message) { case MethodMessage methodMessage: var exception = Wrap.ExecuteSafe(() => { methodMessage.Method.Method = methodMessage.Method.ParentClass.Type.GetMethod(methodMessage.Method.MethodName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public, null, methodMessage.Method.Parameters.Select(x => x.Type.Type).ToArray(), null)!; ExecuteMethod(methodMessage); }); if (exception != null) ResultWriteQueue.Add(new MessageResult(message.MessageID, ApplicationLevel, message.CallerLevel, new SerializableException(exception))); return; case TextMessage textMessage: var handler = TextReceived; if (textMessage.Text != null && handler != null) { foreach (var invoker in handler.GetInvocationList()) { _ = SafeTask.Run(() => ((EventHandler)invoker).Invoke(message.CallerLevel, textMessage.Text)); } } return; case ProgressMessage progressMessage: if (ProgressTasks.TryGetValue(progressMessage.ProgressID, out InterProgress progress)) { _ = SafeTask.Run(() => progress.OnProgressReceived(progressMessage.Value)); } return; case MessageReportMessage reportMessage: if (MessageReportTasks.TryGetValue(reportMessage.ReporterID, out InterMessageReporter reporter)) { _ = SafeTask.Run(() => reporter.OnMessageReceived(reportMessage.Value)); } return; case NodeRegistrationMessage registrationMessage: var registrationException = Wrap.ExecuteSafe(() => { var currentExe = Win32.ProcessEx.GetCurrentProcessFileLocation(); if (Win32.ProcessEx.GetProcessFileLocation(registrationMessage.ProcessID) != currentExe) throw new SecurityException("Process path mismatch."); LevelController.Register(registrationMessage.Level, registrationMessage.ProcessID); LevelController.Open(registrationMessage.Level); ResultWriteQueue.Add(new MessageResult(message.MessageID, message.TargetLevel, message.CallerLevel, new Serializables.SerializableValue(Process.GetCurrentProcess().Id))); }); if (registrationException != null) ResultWriteQueue.Add(new MessageResult(message.MessageID, message.TargetLevel, message.CallerLevel, new SerializableException(registrationException))); else { var registerHandler = NodeRegistered; if (registerHandler != null) { foreach (var invoker in registerHandler.GetInvocationList()) { _ = SafeTask.Run(() => ((EventHandler)invoker).Invoke(null, registrationMessage.Level.ToLevel()), true); } } } return; case TokenCancellationMessage cancellationMessage: var matchingToken = TokenTasks.Values.FirstOrDefault(x => x.SourceLevel == cancellationMessage.SourceLevel && x.ID == cancellationMessage.TokenID) ?? ActiveTokens.Keys.FirstOrDefault(x => x.SourceLevel == cancellationMessage.SourceLevel && x.ID == cancellationMessage.TokenID); if (matchingToken == null) return; matchingToken.Cancel(message.CallerLevel); return; default: throw new IOException($"Unexpected type '{message.GetType()}' received."); } } catch (Exception e) { if (message != null) ResultWriteQueue.Add(new MessageResult(message.MessageID, ApplicationLevel, message.CallerLevel, new SerializableException(e))); else if (jsonResult != null && jsonResult.MessageID.HasValue && jsonResult.Caller.HasValue) ResultWriteQueue.Add(new MessageResult(jsonResult.MessageID.Value, ApplicationLevel, jsonResult.Caller.Value, new SerializableException(e))); throw; } }); } } finally { serverPipe.Dispose(); } }); CancelReceiver(exception ?? new ApplicationException("End of receiver method execution code reached."), true); } private static void ExecuteMethod(MethodMessage message) { var method = message.Method.Method; if (method.IsGenericMethodDefinition) { method = method.MakeGenericMethod(message.Method.GenericTypes.Select(x => x.Type).ToArray()); } foreach (var param in message.Method.Parameters) { if (param.Value is InterCancellationTokenSource icts) { lock (ActiveTokens) { var activeToken = ActiveTokens.FirstOrDefault(kvp => kvp.Key.ID == icts.ID && kvp.Key.SourceLevel == icts.SourceLevel); if (activeToken.Key != null) { activeToken.Value.Add(message.Method); param.Value = activeToken.Key; } else ActiveTokens[icts] = new List { message.Method }; } } } var result = new MessageResult(message.MessageID, ApplicationLevel, message.CallerLevel, (Serializables.SerializableValue)null); try { // We also do this check here for security purposes ThrowIfUnauthorizedMethodAccess(method, ApplicationLevel == InternalLevel.Disposable ? message.CallerLevel.ToTargetLevel() : ApplicationLevel.ToTargetLevel(), message.TargetLevel); receiverCancel.Token.ThrowIfCancellationRequested(); result.Value = new Serializables.SerializableValue(method.ReturnType, method!.Invoke(null, message.Method.Parameters.Select(x => x.Value).ToArray())); if (result.Value.Value is Task task) { task.GetAwaiter().GetResult(); if (task.GetType().IsGenericType) { object taskResult = task.GetType().GetProperty("Result")?.GetValue(task); Type taskType = task.GetType().GetProperty("Result")?.PropertyType; result.Value = taskResult == null ? null : new Serializables.SerializableValue(taskType, taskResult); } } receiverCancel.Token.ThrowIfCancellationRequested(); } catch (Exception e) { result.Value = null; if (e is TargetInvocationException invokeException && invokeException.InnerException != null) result.Exception = new SerializableException(receiverException ?? invokeException.InnerException); else result.Exception = new SerializableException(receiverException ?? e); } foreach (var icts in message.Method.Parameters.Select(x => x.Value).OfType()) { lock (ActiveTokens) { if (ActiveTokens.TryGetValue(icts, out List methods)) { methods.Remove(message.Method); if (methods.Count == 0) { ActiveTokens.TryRemove(icts, out _); icts.Dispose(); } } } } if (receiverCancel.IsCancellationRequested) return; ResultWriteQueue.Add(result); } private static Dictionary _resultSchedulerQueue = new Dictionary(); private static void SendResultThread() { var exception = Wrap.ExecuteSafe(() => { foreach (var result in ResultWriteQueue.GetConsumingEnumerable(receiverCancel.Token)) { var scheduledAction = (Action)(_ => { var json = JsonSerializer.SerializeToUtf8Bytes(result, _serializerOptions); if (json.Length > MaxMessageSize) { var exceptionResult = new MessageResult(result.MessageID, result.MessageTargetLevel, result.MessageCallerLevel, new SerializableException(new SerializationException($"Serialized result data exceeded the maximum message size ({json.Length / 1024}KB > {MaxMessageSize / 1024}KB)."))); json = JsonSerializer.SerializeToUtf8Bytes(exceptionResult, _serializerOptions); } byte[] jsonHash; using (MD5 md5 = new MD5CryptoServiceProvider()) jsonHash = md5.ComputeHash(json); var clientPipe = new NamedPipeClientStream(".", $"{PipePrefix}-{result.MessageCallerLevel}-ResultReceiver", PipeDirection.Out, PipeOptions.None); try { bool retried = false; while (true) { try { clientPipe.ConnectAsync(10000).GetAwaiter().GetResult(); break; } catch (Exception e) { if (retried) throw; Log.EnqueueExceptionSafe(e, null, null, ("Caller", result.MessageCallerLevel), ("Result", result.Value?.Value)); retried = true; clientPipe.Dispose(); Thread.Sleep(100); clientPipe = new NamedPipeClientStream(".", $"{PipePrefix}-{result.MessageCallerLevel}-ResultReceiver", PipeDirection.Out, PipeOptions.None); } } ThrowIfMismatchedServerExePath(clientPipe); ResultTasks.AddOrUpdate(result.MessageID, _ => result, (_, old) => { Log.EnqueueSafe(LogType.Warning, "Overrode matched result MessageID", new SerializableTrace()); return result; }); result.PendingVerification = true; result.JsonHash = jsonHash; WriteJson(clientPipe, json, 5000, CancellationToken.None, null); } finally { clientPipe.Dispose(); } }); if (!_resultSchedulerQueue.TryGetValue(result.MessageCallerLevel, out var task)) _resultSchedulerQueue[result.MessageCallerLevel] = (task = Task.CompletedTask); _resultSchedulerQueue[result.MessageCallerLevel] = task.ContinueWith(scheduledAction); } }, true); CancelReceiver(exception ?? new ApplicationException("End of receiver method execution code reached."), true); } #endregion #region Output private static Thread senderThread = null; private static Thread receiveResultThread = null; private static Exception senderException = null; private static readonly CancellationTokenSource senderCancel = new CancellationTokenSource(); private static readonly BlockingCollection MessageWriteQueue = new BlockingCollection(); private static void ThrowIfSenderViolation(InternalLevel level, bool relaunchIfExited) { senderCancel.Token.ThrowIfCancellationRequested(); LevelController.ThrowIfClosedOrExited(level, relaunchIfExited); if (_mode == Mode.ReceiveOnly) throw new InvalidOperationException($"An attempt was made to send a message from a receive-only level '{level}'."); } private static Dictionary _messageSchedulerQueue = new Dictionary(); private static void SenderThread() { var exception = Wrap.ExecuteSafe(() => { foreach (var message in MessageWriteQueue.GetConsumingEnumerable(senderCancel.Token)) { var scheduledAction = (Action)(_ => { try { if (!(message is ProgressMessage)) { message.MessageID = Guid.NewGuid(); Tasks.AddOrUpdate(message.MessageID, _ => message, (_, old) => { Log.EnqueueSafe(LogType.Warning, "Overrode matched message MessageID", new SerializableTrace()); return message; }); } var json = JsonSerializer.SerializeToUtf8Bytes(message, _serializerOptions); if (json.Length > MaxMessageSize) { SetMessageResult(message, null, new SerializationException($"Serialized message data exceeded the maximum message size ({json.Length / 1024}KB > {MaxMessageSize / 1024}KB).")); return; } using (MD5 md5 = new MD5CryptoServiceProvider()) message.JsonHash = md5.ComputeHash(json); var clientPipe = new NamedPipeClientStream(".", $"{PipePrefix}-{message.TargetLevel}-Receiver", PipeDirection.Out, PipeOptions.None); try { bool retried = false; while (true) { if (!message.Enqueued && message.TargetLevel != InternalLevel.Disposable) ThrowIfSenderViolation(message.TargetLevel, true); try { clientPipe.ConnectAsync(!message.Enqueued ? 10000 : Math.Min(Math.Max(100, message.EnqueueTimeout), 10000)).GetAwaiter().GetResult(); break; } catch (Exception e) { if (message.Enqueued) { message.EnqueueTimeout -= 10000; if (message.EnqueueTimeout < 100) { SetMessageResult(message, null, new TimeoutException(e.Message)); return; } Task.Delay(Math.Min(Math.Max(100, message.EnqueueTimeout), 1000)).ContinueWith(_ => MessageWriteQueue.Add(message)); return; } if (retried) throw; clientPipe.Dispose(); if (message.TargetLevel != InternalLevel.Disposable) ThrowIfSenderViolation(message.TargetLevel, true); Log.EnqueueExceptionSafe(e, null, null, ("Target", message.TargetLevel)); retried = true; Thread.Sleep(100); clientPipe = new NamedPipeClientStream(".", $"{PipePrefix}-{message.TargetLevel}-Receiver", PipeDirection.Out, PipeOptions.None); } } ThrowIfMismatchedServerExePath(clientPipe); message.PendingVerification = true; try { WriteJson(clientPipe, json, 10000, message.TargetLevel == InternalLevel.Disposable ? CancellationToken.None : LevelController.GetCloseToken(message.TargetLevel), message); } catch (OperationCanceledException) { if (message.TargetLevel != InternalLevel.Disposable) LevelController.GetCloseToken(message.TargetLevel).ThrowIfCancellationRequested(); throw new TimeoutException("Took too long to send a message."); } message.Sent = true; if (message is ProgressMessage) SetMessageResult(message, MessageResult.Empty, null); } finally { clientPipe.Dispose(); } } catch (Exception ex) { if (message is MethodMessage methodMessage) Log.EnqueueExceptionSafe(ex, null, null, ("Target", message.TargetLevel), ("Method", methodMessage.Method.MethodName)); else Log.EnqueueExceptionSafe(ex, null, null, ("Target", message.TargetLevel), ("MessageType", message.GetType().Name.Split('.').Last())); SetMessageResult(message, null, senderException ?? ex); } }); if (!_messageSchedulerQueue.TryGetValue(message.TargetLevel, out var task)) _messageSchedulerQueue[message.TargetLevel] = (task = Task.CompletedTask); _messageSchedulerQueue[message.TargetLevel] = task.ContinueWith(scheduledAction); } }); if (!senderCancel.IsCancellationRequested) { foreach (var message in Tasks.Values) SetMessageResult(message, null, senderException ?? exception ?? new ApplicationException("Unexpected SendMethodThread loop exit.")); CancelSender(senderException ??= exception ?? new ApplicationException("End of receiver method execution code reached."), true); } } private static void ReceiveResultThread(PipeSecurity security) { var exception = Wrap.ExecuteSafe(() => { var serverPipe = new NamedPipeServerStream($"{PipePrefix}-{ApplicationLevel}-ResultReceiver", PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, security); try { while (!senderCancel.IsCancellationRequested) { try { serverPipe.WaitForConnectionAsync(senderCancel.Token).GetAwaiter().GetResult(); } catch (IOException) { serverPipe.Dispose(); serverPipe = new NamedPipeServerStream($"{PipePrefix}-{ApplicationLevel}-ResultReceiver", PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, security); continue; } byte[] json; try { ThrowIfMismatchedClientExePath(serverPipe); try { json = ReadJson(serverPipe, 2500, CancellationToken.None, false).Json!; } catch (OperationCanceledException) { throw new TimeoutException("Deserialization took too long."); } } catch (Exception e) { Wrap.ExecuteSafe(() => serverPipe.Disconnect()); Log.EnqueueExceptionSafe(e); continue; } Wrap.ExecuteSafe(() => serverPipe.Disconnect()); SafeTask.Run(async () => { var result = JsonSerializer.Deserialize(json, _serializerOptions); if (result.MessageTargetLevel != InternalLevel.Disposable) ThrowIfSenderViolation(result.MessageTargetLevel, false); using (MD5 md5 = new MD5CryptoServiceProvider()) result.JsonHash = md5.ComputeHash(json); await ThrowIfUnauthorizedResultSender(result); ExitIfHostExited(); if (result.MessageID == Guid.Empty) { Log.EnqueueSafe(LogType.Warning, "Invalid empty result Guid.", null); return; } Tasks.TryRemove(result.MessageID, out InterMessage message); if (message != null) SetMessageResult(message, result, null); }, true); } } finally { serverPipe.Dispose(); } }); if (!senderCancel.IsCancellationRequested) { foreach (var message in Tasks.Values) SetMessageResult(message, null, senderException ?? exception!); CancelSender(exception ?? new ApplicationException("End of receiver method execution code reached."), true); } } #endregion #region Verification private static Thread verificationThread; private static void VerificationThread(PipeSecurity security) { var serverPipe = Wrap.ExecuteSafe(() => new NamedPipeServerStream($"{PipePrefix}-{ApplicationLevel}-VerificationReceiver", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, security), true).Value; try { var exception = Wrap.ExecuteSafe(() => { if (serverPipe == null) throw new ApplicationException("Could not initialize verification receiver."); using var compositeToken = CreateCompositeCancellationTokenSource(senderCancel.Token, receiverCancel.Token); while (!compositeToken.IsCancellationRequested) { try { serverPipe.WaitForConnectionAsync(compositeToken.Token).GetAwaiter().GetResult(); } catch (IOException) { serverPipe.Dispose(); serverPipe = new NamedPipeServerStream($"{PipePrefix}-{ApplicationLevel}-VerificationReceiver", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, security); continue; } byte[] verified = new byte[1]; try { ThrowIfMismatchedClientExePath(serverPipe); byte[] json; try { json = ReadJson(serverPipe, 5000, CancellationToken.None, false).Json; } catch (OperationCanceledException) { throw new TimeoutException("Deserialization took too long."); } var request = JsonSerializer.Deserialize(json, _serializerOptions); switch (request.Type) { case VerificationType.Message: senderCancel.Token.ThrowIfCancellationRequested(); verified[0] = VerifyMessage(request) ? (byte)1 : (byte)0; break; case VerificationType.Result: receiverCancel.Token.ThrowIfCancellationRequested(); verified[0] = VerifyResult(request) ? (byte)1 : (byte)0; break; default: Wrap.ExecuteSafe(() => serverPipe.Disconnect()); Log.EnqueueSafe(LogType.Warning, "Unkown verification request type received.", null, null, ("Request Type", request.Type), ("Request Caller", request.CallerLevel)); continue; } try { using (_ = new SynchronousIoCanceler(5000)) serverPipe.Write(verified, 0, verified.Length); } catch (OperationCanceledException) { throw new TimeoutException("Verification write took too long."); } if (ApplicationLevel == InternalLevel.Disposable && verified[0] == 1 && request.Type == VerificationType.Result) Environment.Exit(0); } catch (Exception e) { Wrap.ExecuteSafe(() => serverPipe.Disconnect()); Log.EnqueueExceptionSafe(e); continue; } Wrap.ExecuteSafe(() => serverPipe.Disconnect()); } }); if (ApplicationLevel == InternalLevel.Disposable) { Log.WriteExceptionSafe(exception); Environment.Exit(57); } Log.EnqueueExceptionSafe(exception); CloseConnection(false); } finally { serverPipe?.Dispose(); } } public static CancellationTokenSource CreateCompositeCancellationTokenSource(CancellationToken token1, CancellationToken token2) { var linkedTokenSource = new CancellationTokenSource(); token1.Register(() => { if (token1.IsCancellationRequested && token2.IsCancellationRequested) linkedTokenSource.Cancel(); }); token2.Register(() => { if (token1.IsCancellationRequested && token2.IsCancellationRequested) linkedTokenSource.Cancel(); }); return linkedTokenSource; } private static bool VerifyMessage(VerificationRequest request) { var verifiedMessage = Tasks.Values.FirstOrDefault(message => { var verified = message.PendingVerification && message.MessageID == request.IdToVerify && message.TargetLevel == request.CallerLevel && message.CallerLevel == request.TargetLevel && message.JsonHash.SequenceEqual(request.JsonHash); if (verified) message.PendingVerification = false; return verified; }); if (verifiedMessage != null && !(verifiedMessage is MethodMessage || verifiedMessage is NodeRegistrationMessage)) SetMessageResult(verifiedMessage, MessageResult.Empty, null); return verifiedMessage != null; } private static bool VerifyResult(VerificationRequest request) { return ResultTasks.Values.Any(result => { bool verified = result.PendingVerification && result.MessageID == request.IdToVerify && result.MessageTargetLevel == request.TargetLevel && result.MessageCallerLevel == request.CallerLevel && result.JsonHash.SequenceEqual(request.JsonHash); if (verified) result.PendingVerification = false; return verified; }); } #endregion } } ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) Ameliorated LLC. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: ManagedWimLib/Compressors/Compressor.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using System; using System.Diagnostics; using System.Runtime.CompilerServices; namespace ManagedWimLib.Compressors { public class Compressor : IDisposable { #region (static) LoadManager private static WimLibLoadManager Manager => Wim.Manager; private static WimLibLoader Lib => Wim.Manager.Lib; #endregion #region Fields private IntPtr _ptr; #endregion #region Constructor (private) private Compressor(IntPtr ptr) { _ptr = ptr; } #endregion #region Disposable Pattern ~Compressor() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposing) return; if (_ptr == IntPtr.Zero) return; Lib.FreeCompressor(_ptr); _ptr = IntPtr.Zero; } #endregion #region Create - (Static) CreateCompressor /// /// Allocate a compressor for the specified compression type using the specified parameters. /// This function is part of wimlib's compression API; it is not necessary to call this to process a WIM file. /// /// /// Compression type for which to create the compressor, as one of the constants. /// /// /// The maximum compression block size to support. /// This specifies the maximum allowed value for the uncompressedSize parameter of when called using this compressor. /// Usually, the amount of memory used by the compressor will scale in proportion to the parameter. /// can be used to query the specific amount of memory that will be required. /// This parameter must be at least 1 and must be less than or equal to a compression-type-specific limit. /// /// /// The compression level to use. /// If 0, the default compression level (50, or another value as set through /// ) is used. /// Otherwise, a higher value indicates higher compression.
/// The values are scaled so that 10 is low compression, 50 is medium compression, and 100 is high compression. /// This is not a percentage; values above 100 are also valid.
/// /// /// Flag creates the compressor in a mode where it is allowed to modify the input buffer. /// Specifically, in this mode, if compression succeeds, the input buffer may have been modified, /// whereas if compression does not succeed the input buffer still may have been written to but will have been restored exactly to its original state. /// This mode is designed to save some memory when using large buffer sizes. /// /// /// On success, a new instance of the allocated , which can be used for any number of calls to . /// This instance must be disposed manually. /// /// wimlib did not return . public static Compressor Create(CompressionType ctype, int maxBlockSize, uint compressionLevel, CompressorFlags compressorFlags) { Manager.EnsureLoaded(); if (maxBlockSize < 0) throw new ArgumentOutOfRangeException(nameof(maxBlockSize)); compressionLevel |= (uint)compressorFlags; ErrorCode ret = Lib.CreateCompressor(ctype, new UIntPtr((uint)maxBlockSize), compressionLevel, out IntPtr compPtr); WimLibException.CheckErrorCode(ret); return new Compressor(compPtr); } #endregion #region Compress (Safe) /// /// Compress a buffer of data. /// /// /// Buffer containing the data to compress. /// The length of the span cannot be greater than the maxBlockSize with which was called. /// (If it is, the data will not be compressed and 0 will be returned.) /// /// /// Buffer into which to write the compressed data. /// The length of the span cannot exceed the maxBlockSize with which was called. /// (If it does, the data will not be decompressed and a nonzero value will be returned.) /// /// /// The size of the compressed data, in bytes, or 0 if the data could not be compressed to or fewer bytes. /// /// Used a size greater than uint.MaxValue in 32bit platform. public unsafe int Compress(ReadOnlySpan uncompressedSpan, Span compressedSpan) { UIntPtr compressedBytes; fixed (byte* uncompressedBuf = uncompressedSpan) fixed (byte* compressedBuf = compressedSpan) { UIntPtr uncompressedSize = new UIntPtr((uint)uncompressedSpan.Length); UIntPtr compressedSizeAvail = new UIntPtr((uint)compressedSpan.Length); compressedBytes = Lib.Compress(uncompressedBuf, uncompressedSize, compressedBuf, compressedSizeAvail, _ptr); } // Since compressedSizeAvail is int, the returned value cannot be larger than int.MaxValue. ulong ret = compressedBytes.ToUInt64(); return (int)ret; } /// /// Compress a buffer of data. /// /// /// Buffer containing the data to compress. /// /// /// The offset of the data in buffer containing the data to compress. /// /// /// Size, in bytes, of the data to compress. /// This cannot be greater than the maxBlockSize with which was called.
/// (If it is, the data will not be compressed and 0 will be returned.)
/// /// /// Buffer into which to write the compressed data. /// /// /// The offset of the data in buffer into which to write the compressed data. /// /// /// Number of bytes available in . /// /// /// The size of the compressed data, in bytes, or 0 if the data could not be compressed to or fewer bytes. /// public unsafe int Compress(byte[] uncompressedData, int uncompressedOffset, int uncompressedSize, byte[] compressedData, int compressedOffset, int compressedSizeAvail) { CheckReadWriteArgs(uncompressedData, uncompressedOffset, uncompressedSize); CheckReadWriteArgs(compressedData, compressedOffset, compressedSizeAvail); ulong compressedSize; fixed (byte* uncompressedBuf = uncompressedData.AsSpan(uncompressedOffset, uncompressedSize)) fixed (byte* compressedBuf = compressedData.AsSpan(compressedOffset, compressedSizeAvail)) { compressedSize = Compress(uncompressedBuf, (ulong)uncompressedSize, compressedBuf, (ulong)compressedSizeAvail); } // Since compressedSizeAvail is int, the returned value cannot be larger than int.MaxValue. Debug.Assert(compressedSize <= int.MaxValue); return (int)compressedSize; } #endregion #region Compress (Unsafe) /// /// Compress a buffer of data. /// /// /// Buffer containing the data to compress. /// /// /// Size, in bytes, of the data to compress. /// This cannot be greater than the maxBlockSize with which was called.
/// (If it is, the data will not be compressed and 0 will be returned.)
/// /// /// Buffer into which to write the compressed data. /// /// /// Number of bytes available in . /// /// /// The size of the compressed data, in bytes, or 0 if the data could not be compressed to or fewer bytes. /// /// Used a size greater than uint.MaxValue in 32bit platform. public unsafe ulong Compress(byte* uncompressedBuf, ulong uncompressedSize, byte* compressedBuf, ulong compressedSizeAvail) { UIntPtr uncompressedSizeInterop = new UIntPtr(uncompressedSize); UIntPtr compressedSizeAvailInterop = new UIntPtr(compressedSizeAvail); UIntPtr compressedBytes = Lib.Compress(uncompressedBuf, uncompressedSizeInterop, compressedBuf, compressedSizeAvailInterop, _ptr); ulong ret = compressedBytes.ToUInt64(); return ret; } #endregion #region (Utility) CheckReadWriteArgs [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void CheckReadWriteArgs(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (buffer.Length - offset < count) throw new ArgumentOutOfRangeException(nameof(count)); } #endregion } } ================================================ FILE: ManagedWimLib/Compressors/Decompressor.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using System; using System.Runtime.CompilerServices; namespace ManagedWimLib.Compressors { public class Decompressor : IDisposable { #region (static) LoadManager private static WimLibLoadManager Manager => Wim.Manager; private static WimLibLoader Lib => Wim.Manager.Lib; #endregion #region Fields private IntPtr _ptr; #endregion #region Constructor (private) private Decompressor(IntPtr ptr) { _ptr = ptr; } #endregion #region Disposable Pattern ~Decompressor() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposing) return; if (_ptr == IntPtr.Zero) return; Lib.FreeDecompressor(_ptr); _ptr = IntPtr.Zero; } #endregion #region Create - (Static) CreateDecompressor /// /// Allocate a decompressor for the specified compression type. /// This function is part of wimlib's compression API; it is not necessary to call this to process a WIM file. /// /// /// Compression type for which to create the decompressor, as one of the constants. /// /// /// The maximum compression block size to support. /// This specifies the maximum allowed value for the uncompressedSize parameter of when called using this decompressor. /// In general, this parameter must be the same as the maxBlockSize that was passed to when the data was compressed. /// However, some compression types have looser requirements regarding this. /// /// /// On success, a new instance of the allocated , which can be used for any number of calls to . /// This instance must be disposed manually. /// /// wimlib did not return . public static Decompressor Create(CompressionType ctype, int maxBlockSize) { Manager.EnsureLoaded(); if (maxBlockSize < 0) throw new ArgumentOutOfRangeException(nameof(maxBlockSize)); ErrorCode ret = Lib.CreateDecompressor(ctype, new UIntPtr((uint)maxBlockSize), out IntPtr decompPtr); WimLibException.CheckErrorCode(ret); return new Decompressor(decompPtr); } #endregion #region Decompress (Safe) /// /// Decompress a buffer of data. Return true on success. /// This function requires that the exact uncompressed size of the data be passed as the parameter.
/// If this is not done correctly, decompression may fail or the data may be decompressed incorrectly.
///
/// /// Buffer containing the data to decompress. /// /// /// Buffer into which to write the uncompressed data. /// /// /// Size, in bytes, of the data when uncompressed. /// This cannot exceed the maxBlockSize with which was called.
/// (If it does, the data will not be decompressed and a nonzero value will be returned.)
/// /// /// Return true on success, false on failure. /// /// Used a size greater than uint.MaxValue in 32bit platform. public unsafe bool Decompress(ReadOnlySpan compressedSpan, Span uncompressedSpan, int exactUncompressedSize) { if (exactUncompressedSize < 0) throw new ArgumentOutOfRangeException(nameof(exactUncompressedSize)); if (exactUncompressedSize < uncompressedSpan.Length) throw new ArgumentOutOfRangeException($"{nameof(uncompressedSpan)} must be equal to or larger than {nameof(exactUncompressedSize)}."); fixed (byte* compressedBuf = compressedSpan) fixed (byte* uncompressedBuf = uncompressedSpan) { return Decompress(compressedBuf, (ulong)compressedSpan.Length, uncompressedBuf, (ulong)exactUncompressedSize); } } /// /// Decompress a buffer of data. Return true on success. /// This function requires that the exact uncompressed size of the data be passed as the parameter.
/// If this is not done correctly, decompression may fail or the data may be decompressed incorrectly.
///
/// /// Buffer containing the data to decompress. /// /// /// The offset of the data to decompress. /// /// /// Size, in bytes, of the data to decompress. /// /// /// Buffer into which to write the uncompressed data. /// /// /// The offset of the buffer into which to write the uncompressed data. /// /// /// Size, in bytes, of the data when uncompressed. /// This cannot exceed the maxBlockSize with which was called.
/// (If it does, the data will not be decompressed and a nonzero value will be returned.)
/// /// Return true on success. public unsafe bool Decompress(byte[] compressedData, int compressedOffset, int compressedSize, byte[] uncompressedData, int uncompressedOffset, int exactUncompressedSize) { CheckReadWriteArgs(compressedData, compressedOffset, compressedSize); CheckReadWriteArgs(uncompressedData, uncompressedOffset, exactUncompressedSize); fixed (byte* compressedBuf = compressedData.AsSpan(compressedOffset, compressedSize)) fixed (byte* uncompressedBuf = uncompressedData.AsSpan(uncompressedOffset, exactUncompressedSize)) { return Decompress(compressedBuf, (ulong)compressedSize, uncompressedBuf, (ulong)exactUncompressedSize); } } #endregion #region Decompress (Unsafe) /// /// Decompress a buffer of data. Return true on success. /// This function requires that the exact uncompressed size of the data be passed as the parameter.
/// If this is not done correctly, decompression may fail or the data may be decompressed incorrectly.
///
/// /// Buffer containing the data to decompress. /// /// /// Size, in bytes, of the data to decompress. /// /// /// Buffer into which to write the uncompressed data. /// /// /// Size, in bytes, of the data when uncompressed. /// This cannot exceed the maxBlockSize with which was called.
/// (If it does, the data will not be decompressed and a nonzero value will be returned.)
/// /// Return true on success. /// Used a size greater than uint.MaxValue in 32bit platform. public unsafe bool Decompress(byte* compressedBuf, ulong compressedSize, byte* uncompressedBuf, ulong exactUncompressedSize) { UIntPtr compressedSizeInterop = new UIntPtr(compressedSize); UIntPtr uncompressedSizeInterop = new UIntPtr(exactUncompressedSize); // 0 on success, Non-0 on failure. int ret = Lib.Decompress(compressedBuf, compressedSizeInterop, uncompressedBuf, uncompressedSizeInterop, _ptr); return ret == 0; } #endregion #region (Utility) CheckReadWriteArgs [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void CheckReadWriteArgs(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (buffer.Length - offset < count) throw new ArgumentOutOfRangeException(nameof(count)); } #endregion } } ================================================ FILE: ManagedWimLib/Helper.cs ================================================ /* Licensed under LGPLv3 C# Wrapper written by Hajin Jang Copyright (C) 2017-2019 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using System; using System.Text; namespace ManagedWimLib { #region StringHelper internal class StringHelper { public static string ReplaceEx(string str, string oldValue, string newValue, StringComparison comp) { if (oldValue.Length == 0) return str; if (str.IndexOf(oldValue, comp) == -1) return str; int idx = 0; StringBuilder b = new StringBuilder(); while (idx < str.Length) { int vIdx = str.IndexOf(oldValue, idx, comp); if (vIdx == -1) { b.Append(str.Substring(idx)); break; } b.Append(str.Substring(idx, vIdx - idx)); b.Append(newValue); idx = vIdx + oldValue.Length; } return b.ToString(); } } #endregion } ================================================ FILE: ManagedWimLib/IterateCallback.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2017-2019 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using System; using System.Runtime.InteropServices; namespace ManagedWimLib { #region IterateDirTreeCallback /// /// Type of a callback function to . /// Must return (0) on success. /// /// Use negative integer for custom non-sucess return value. /// may return positive integer when the error occured, /// and it is hard to distinct it from user-returned positive integer. /// public delegate int IterateDirTreeCallback(DirEntry dentry, object userData); internal class ManagedIterateDirTreeCallback { private readonly IterateDirTreeCallback _callback; private readonly object _userData; internal WimLibLoader.NativeIterateDirTreeCallback NativeFunc { get; } public ManagedIterateDirTreeCallback(IterateDirTreeCallback callback, object userData) { _callback = callback; _userData = userData; // Avoid GC by keeping ref here NativeFunc = NativeCallback; } private int NativeCallback(IntPtr entryPtr, IntPtr userCtx) { if (_callback == null) return Wim.IterateCallbackSuccess; // Default return value is a value represents Success/Continue. int ret; DirEntryBase b = Marshal.PtrToStructure(entryPtr); DirEntry dentry = new DirEntry { FileName = b.FileName, DosName = b.DosName, FullPath = b.FullPath, Depth = b.Depth, SecurityDescriptor = b.SecurityDescriptor, Attributes = b.Attributes, ReparseTag = b.ReparseTag, NumLinks = b.NumLinks, NumNamedStreams = b.NumNamedStreams, HardLinkGroupId = b.HardLinkGroupId, CreationTime = b.CreationTime, LastWriteTime = b.LastWriteTime, LastAccessTime = b.LastAccessTime, UnixUserId = b.UnixUserId, UnixGroupId = b.UnixGroupId, UnixMode = b.UnixMode, UnixRootDevice = b.UnixRootDevice, ObjectId = b.ObjectId, Streams = new StreamEntry[b.NumNamedStreams + 1], }; IntPtr baseOffset = IntPtr.Add(entryPtr, Marshal.SizeOf()); for (int i = 0; i < dentry.Streams.Length; i++) { IntPtr offset = IntPtr.Add(baseOffset, i * Marshal.SizeOf()); dentry.Streams[i] = Marshal.PtrToStructure(offset); } ret = _callback(dentry, _userData); return ret; } } #endregion #region IterateLookupTableCallback /// /// Type of a callback function to . /// Must return (0) on success. /// public delegate int IterateLookupTableCallback(ResourceEntry resource, object userCtx); internal class ManagedIterateLookupTableCallback { private readonly IterateLookupTableCallback _callback; private readonly object _userData; internal WimLibLoader.NativeIterateLookupTableCallback NativeFunc { get; } public ManagedIterateLookupTableCallback(IterateLookupTableCallback callback, object userData) { _callback = callback; _userData = userData; // Avoid GC by keeping ref here NativeFunc = NativeCallback; } private int NativeCallback(ResourceEntry resource, IntPtr userCtx) { if (_callback == null) return Wim.IterateCallbackSuccess; return _callback(resource, _userData); } } #endregion } ================================================ FILE: ManagedWimLib/ManagedWimLib.csproj ================================================  v4.7.2 latest true ManagedWimLib ManagedWimLib x86\%(FileName)%(Extension) PreserveNewest x64\%(FileName)%(Extension) PreserveNewest arm64\%(FileName)%(Extension) PreserveNewest runtimes\win-x86\native\%(FileName)%(Extension) PreserveNewest runtimes\win-x64\native\%(FileName)%(Extension) PreserveNewest runtimes\win-arm64\native\%(FileName)%(Extension) PreserveNewest runtimes\linux-x64\native\%(FileName)%(Extension) PreserveNewest runtimes\linux-arm\native\%(FileName)%(Extension) PreserveNewest runtimes\linux-arm64\native\%(FileName)%(Extension) PreserveNewest runtimes\osx-x64\native\%(FileName)%(Extension) PreserveNewest runtimes\osx-arm64\native\%(FileName)%(Extension) PreserveNewest ================================================ FILE: ManagedWimLib/ManagedWimLib.netfx.targets ================================================ true x86\%(FileName)%(Extension) PreserveNewest x64\%(FileName)%(Extension) PreserveNewest x64\%(FileName)%(Extension) PreserveNewest bin\x86\%(Filename)%(Extension) bin\x64\%(Filename)%(Extension) bin\arm64\%(Filename)%(Extension) $(PostBuildEventDependsOn); CopyInteropLibFiles_x86; CopyInteropLibFiles_x64 CopyInteropLibFiles_arm64; $(BuildDependsOn); CopyInteropLibFiles_x86; CopyInteropLibFiles_x64; CopyInteropLibFiles_arm64; $(CleanDependsOn); CleanInteropLibFiles_x86; CleanInteropLibFiles_x64 CleanInteropLibFiles_arm64; CollectInteropLibFiles_x86; CollectInteropLibFiles_x64 CollectInteropLibFiles_arm64; $(PipelineCollectFilesPhaseDependsOn); ================================================ FILE: ManagedWimLib/NUGET_README.md ================================================ # ManagedWimLib Cross-platform [wimlib](https://wimlib.net) pinvoke library for .NET. [wimlib](https://wimlib.net) is a library that handles Windows Imaging (WIM) archives, written by [Eric Biggers](https://github.com/ebiggers). ## Support ### Targeted .NET platforms - .NET Core 3.1 - .NET Standard 2.0 - .NET Framework 4.6 #### Discontinued frameworks | Platform | Last Supported Version | |----------|------------------------| | .NET Standard 1.3 | [v1.1.2](https://www.nuget.org/packages/ManagedWimLib/1.1.2) | | .NET Framework 4.5 | [v1.2.4](https://www.nuget.org/packages/ManagedWimLib/1.2.4) | | .NET Framework 4.5.1 | [v2.4.0](https://www.nuget.org/packages/ManagedWimLib/2.4.0) | ### Supported OS platforms | Platform | Architecture | Tested | |----------|--------------|--------| | Windows | x86 | Yes | | | x64 | Yes | | | arm64 | Yes | | Linux | x64 | Yes | | | arm | Yes | | | arm64 | Yes | | macOS | x64 | Yes | | | arm64 | Yes | ### Supported wimlib version - 1.14.3 (Included) ## Usage Please refer to the project homepage. ================================================ FILE: ManagedWimLib/NativeStructs.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2017-2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ // ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMember.Global // ReSharper disable StringLiteralTypo // ReSharper disable ClassNeverInstantiated.Global // ReSharper disable InconsistentNaming // ReSharper disable EnumUnderlyingTypeIsInt // ReSharper disable FieldCanBeMadeReadOnly.Local // ReSharper disable PrivateFieldCanBeConvertedToLocalVariable using Joveler.DynLoader; using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; #pragma warning disable 169 #pragma warning disable 414 #pragma warning disable 649 #pragma warning disable IDE0044 #pragma warning disable IDE0052 // private namespace ManagedWimLib { #region Native wimlib enums #region enum CompressionType /// /// Specifies a compression type. /// /// A WIM file has a default compression type, indicated by its file header. /// Normally, each resource in the WIM file is compressed with this compression type. /// However, resources may be stored as uncompressed; for example, /// wimlib may do so if a resource does not compress to less than its original size. /// In addition, a WIM with the new version number of 3584, or "ESD file", /// might contain solid resources with different compression types. /// public enum CompressionType : int { /// /// No compression. /// This is a valid argument to and , /// but not to the functions in the compression API such as . /// None = 0, /// /// The XPRESS compression format. /// This format combines Lempel-Ziv factorization with Huffman encoding. /// Compression and decompression are both fast. /// /// This format supports chunk sizes that are powers of 2 between 2^12 and 2^16, inclusively. /// If using to create /// an XPRESS compressor directly, the maxBlockSize parameter may be any positive value up to and including 2^16. /// XPRESS = 1, /// /// The LZX compression format. /// This format combines Lempel-Ziv factorization with Huffman encoding, but with more features and complexity than XPRESS. /// Compression is slow to somewhat fast, depending on the settings. /// Decompression is fast but slower than XPRESS. /// /// This format supports chunk sizes that are powers of 2 between 2^15 and 2^21, inclusively. /// Note: chunk sizes other than 2^15 are not compatible with the Microsoft implementation. /// If using to create /// an LZX compressor directly, the maxBlockSize parameter may be any positive value up to and including 2^21. /// LZX = 2, /// /// The LZMS compression format. /// This format combines Lempel-Ziv factorization with adaptive Huffman encoding and range coding. /// Compression and decompression are both fairly slow. /// /// This format supports chunk sizes that are powers of 2 between 2^15 and 2^30, inclusively. /// This format is best used for large chunk sizes. /// If using to create /// an LZMS compressor directly, the maxBlockSize parameter may be any positive value up to and including 2^30. /// LZMS = 3, } #endregion #region enum ProgressMsg /// /// Possible values of the first parameter to the user-supplied ProgressFunc callback /// public enum ProgressMsg { /// /// A WIM image is about to be extracted. /// info param will point to . /// This message is received once per image for calls to . /// ExtractImageBegin = 0, /// /// One or more file or directory trees within a WIM image is about to be extracted. /// info param will point to . /// This message is received only once per and , /// since wimlib combines all paths into a single extraction operation for optimization purposes. /// ExtractTreeBegin = 1, /// /// This message may be sent periodically (not for every file) while files and directories are being created, prior to file data extraction. /// info param will point to . /// In particular, the CurrentFileCount and EndFileCount members may be used to track the progress of this phase of extraction. /// ExtractFileStructure = 3, /// /// File data is currently being extracted. /// info param will point to . /// This is the main message to track the progress of an extraction operation. /// ExtractStreams = 4, /// /// Starting to read a new part of a split pipable WIM over the pipe. /// info param will point to . /// ExtractSpwmPartBegin = 5, /// /// This message may be sent periodically (not necessarily for every file) while file and directory metadata is being extracted, /// following file data extraction. /// info param will point to . /// The and members /// may be used to track the progress of this phase of extraction. /// ExtractMetadata = 6, /// /// The image has been successfully extracted. /// info param will point to . /// This is paired with . /// ExtractImageEnd = 7, /// /// The files or directory trees have been successfully extracted. /// info param will point to . /// This is paired with . /// ExtractTreeEnd = 8, /// /// The directory or NTFS volume is about to be scanned for metadata. /// info param will point to . /// This message is received once per call to , /// or once per capture source passed to , /// or once per add command passed to . /// ScanBegin = 9, /// /// A directory or file has been scanned. /// info param will point to , and its member will be valid. /// This message is only sent if has been specified. /// ScanDEntry = 10, /// /// The directory or NTFS volume has been successfully scanned. /// info param will point to . /// This is paired with a previous message, /// possibly with many intervening messages. /// ScanEnd = 11, /// /// File data is currently being written to the WIM. /// info param will point to . /// This message may be received many times while the WIM file is being written or appended /// to with , . /// Since wimlib v1.13.4 it will also be received when a split WIM part is being written by . /// WriteStreams = 12, /// /// Per-image metadata is about to be written to the WIM file. /// info param will not be valid. /// WriteMetadataBegin = 13, /// /// The per-image metadata has been written to the WIM file. /// info param will not be valid. /// This message is paired with a preceding message. /// WriteMetadataEnd = 14, /// /// has successfully renamed the temporary file to the original WIM file, /// thereby committing the changes to the WIM file. /// info param will point to . /// Note: this message is not received if chose to append to the WIM file in-place. /// Rename = 15, /// /// The contents of the WIM file are being checked against the integrity table. /// info param will point to . /// This message is only received (and may be received many times) when with /// is called with the flag. /// VerifyIntegrity = 16, /// /// An integrity table is being calculated for the WIM being written. /// info param will point to . /// This message is only received (and may be received many times) /// when a WIM file is being written with the flag . /// CalcIntegrity = 17, /// /// A operation is in progress, and a new split part is about to be started. /// info param will point to . /// SplitBeginPart = 19, /// /// A operation is in progress, and a split part has been finished. /// info param will point to . /// SplitEndPart = 20, /// /// A WIM update command is about to be executed. /// info param will point to . /// This message is received once per update command when is called with the flag . /// UpdateBeginCommand = 21, /// /// A WIM update command has been executed. /// info param will point to . /// This message is received once per update command when is called with the flag . /// UpdateEndCommand = 22, /// /// A file in the image is being replaced as a result of a without specified. /// info param will point to . /// This is only received when is also specified in the add command. /// ReplaceFileInWim = 23, /// /// An image is being extracted with , /// and a file is being extracted normally (not as a "WIMBoot pointer file") /// due to it matching a pattern in the PrepopulateList section of the configuration file /// /Windows/System32/WimBootCompress.ini in the WIM image. /// info param will point to . /// WimBootExclude = 24, /// /// Starting to unmount an image. /// info param will point to . /// UnmountBegin = 25, /// /// wimlib has used a file's data for the last time (including all data streams, if it has multiple). /// info param will point to . /// This message is only received if was provided. /// DoneWithFile = 26, /// /// is starting to verify the metadata for an image. /// info param will point to . /// BeginVerifyImage = 27, /// /// has finished verifying the metadata for an image. /// info param will point to . /// EndVerifyImage = 28, /// /// is verifying file data integrity. /// info param will point to . /// VerifyStreams = 29, /// /// The progress function is being asked whether a file should be excluded from capture or not. /// info param will point to . /// This is a bidirectional message that allows the progress function to set a flag if the file should be excluded. /// /// This message is only received if the flag is used. /// This method for file exclusions is independent of the "capture configuration file" mechanism. /// TestFileExclusion = 30, /// /// An error has occurred and the progress function is being asked whether to ignore the error or not. /// info param will point to . /// This is a bidirectional message. /// /// This message provides a limited capability for applications to recover from "unexpected" errors /// (i.e. those with no in-library handling policy) arising from the underlying operating system. /// Normally, any such error will cause the library to abort the current operation. /// By implementing a handler for this message, the application can instead choose to ignore a given error. /// /// Currently, only the following types of errors will result in this progress message being sent: /// /// - Directory tree scan errors, e.g. from /// - Most extraction errors; currently restricted to the Windows build of the library only. /// HandleError = 31, } /// /// A pointer to this union is passed to the user-supplied ProgressFunc callback. /// One (or none) of the structures contained in this union will be applicable for the operation (ProgressMsg) /// indicated in the first argument to the callback. /// public enum CallbackStatus : int { /// /// The operation should be continued. /// This is the normal return value. /// Continue = 0, /// /// The operation should be aborted. /// This will cause the current operation to fail with ErrorCode.AbortedByProgress. /// Abort = 1, } #endregion #region enum ErrorCode /// /// Possible values of the error code returned by many functions in wimlib. /// See the documentation for each wimlib function to see specifically what error codes can be returned by a given function, and what they mean. /// public enum ErrorCode : int { Success = 0, /// /// Another process is currently modifying the WIM file. /// AlreadyLocked = 1, Decompression = 2, /// /// A non-zero status code was returned by fuse_main(). /// Fuse = 6, GlobHadNoMatches = 8, /// /// The number of metadata resources found in the WIM did not match the image count specified in the WIM header, /// or the number of <IMAGE> elements in the XML data of the WIM did not match the image count specified in the WIM header. /// ImageCount = 10, ImageNameCollision = 11, InsufficientPrivileges = 12, /// /// was specified in openFlags, and the WIM file failed the integrity check. /// Integrity = 13, InvalidCaptureConfig = 14, /// /// The library did not recognize the compression chunk size of the WIM as valid for its compression type. /// InvalidChunkSize = 15, /// /// The library did not recognize the compression type of the WIM. /// InvalidCompressionType = 16, /// /// The header of the WIM was otherwise invalid. /// InvalidHeader = 17, /// /// An image does not exist in a . /// InvalidImage = 18, /// /// was specified in openFlags and the WIM contained an integrity table, /// but the integrity table was invalid. /// InvalidIntegrityTable = 19, /// /// The lookup table of the WIM was invalid. /// InvalidLookupTableEntry = 20, InvalidMetadataResource = 21, InvalidOverlay = 23, /// /// was null; or, wimFile was not a nonempty string. /// InvalidParam = 24, InvalidPartNumber = 25, InvalidPipableWim = 26, InvalidReparseData = 27, InvalidResourceHash = 28, InvalidUtf16String = 30, InvalidUtf8String = 31, IsDirectory = 32, /// /// The WIM was a split WIM and was specified in openFlags. /// IsSplitWim = 33, Link = 35, MetadataNotFound = 36, /// /// was specified in mountFlags, but the staging directory could not be created. /// MkDir = 37, MQueue = 38, NoMem = 39, NotDir = 40, NotEmpty = 41, NotARegularFile = 42, /// /// The file did not begin with the magic characters that identify a WIM file. /// NotAWimFile = 43, NotPipable = 44, NoFilename = 45, Ntfs3G = 46, /// /// Failed to open the WIM file for reading. /// Some possible reasons: the WIM file does not exist, or the calling process does not have permission to open it. /// Open = 47, /// /// Failed to read data from the WIM file. /// OpenDir = 48, PathDoesNotExist = 49, Read = 50, Readlink = 51, Rename = 52, ReparsePointFixupFailed = 54, ResourceNotFound = 55, ResourceOrder = 56, SetAttributes = 57, SetReparseData = 58, SetSecurity = 59, SetShortName = 60, SetTimestamps = 61, SplitInvalid = 62, Stat = 63, /// /// Unexpected end-of-file while reading data from the WIM file. /// UnexpectedEndOfFile = 65, UnicodeStringNotRepresentable = 66, /// /// The WIM version number was not recognized. (May be a pre-Vista WIM.) /// UnknownVersion = 67, Unsupported = 68, UnsupportedFile = 69, /// /// was specified but the WIM file was considered read-only because of /// any of the reasons mentioned in the documentation for the flag. /// WimIsReadOnly = 71, Write = 72, /// /// The XML data of the WIM was invalid. /// XML = 73, /// /// The WIM cannot be opened because it contains encrypted segments. (It may be a Windows 8+ "ESD" file.) /// WimIsEncrypted = 74, WimBoot = 75, AbortedByProgress = 76, UnknownProgressStatus = 77, MkNod = 78, MountedImageIsBusy = 79, NotAMountPoint = 80, NotPermittedToUnmount = 81, FveLockedVolume = 82, UnableToReadCaptureConfig = 83, /// /// The WIM file is not complete (e.g. the program which wrote it was terminated before it finished) /// WimIsIncomplete = 84, CompactionNotPossible = 85, /// /// There are currently multiple references to the image as a result of a call to . /// Free one before attempting the read-write mount. /// ImageHasMultipleReferences = 86, DuplicateExportedImage = 87, ConcurrentModificationDetected = 88, SnapshotFailure = 89, InvalidXAttr = 90, SetXAttr = 91, } #endregion #region enum IterateDirTreeFlags [Flags] public enum IterateDirTreeFlags : uint { None = 0x00000000, /// /// For : /// Iterate recursively on children rather than just on the specified path. /// Recursive = 0x00000001, /// /// For : /// Don't iterate on the file or directory itself; only its children (in the case of a non-empty directory) /// Children = 0x00000002, /// /// Return if any file data blobs needed to fill in the 's for the iteration /// cannot be found in the blob lookup table of the . /// The default behavior without this flag is to fill in the and set " flag. /// ResourcesNeeded = 0x00000004, } #endregion #region enum IterateLookupTableFlags /// /// Reserved for /// [Flags] public enum IterateLookupTableFlags : uint { None = 0x00000000, } #endregion #region enum AddFlags [Flags] public enum AddFlags : uint { None = 0x00000000, /// /// UNIX-like systems only: /// Directly capture an NTFS volume rather than a generic directory. /// This requires that wimlib was compiled with support for libntfs-3g. /// /// This flag cannot be combined with or . /// /// Do not use this flag on Windows, /// where wimlib already supports all Windows-native filesystems, including NTFS, through the Windows APIs. /// Ntfs = 0x00000001, /// /// Follow symbolic links when scanning the directory tree. /// Currently only supported on UNIX-like systems. /// Dereference = 0x00000002, /// /// Call the progress function with the message when each directory or file has been scanned. /// Verbose = 0x00000004, /// /// Mark the image being added as the bootable image of the WIM. /// This flag is valid only for and . /// /// Note that you can also change the bootable image of a WIM using . /// /// Note: does something different from, and independent from, . /// Boot = 0x00000008, /// /// UNIX-like systems only: /// Store the UNIX owner, group, mode, and device ID (major and minor number) of each file. /// In addition, capture special files such as device nodes and FIFOs. /// Since wimlib v1.11.0, on Linux also capture extended attributes. /// See the documentation for the "--unix-data" option to wimcapture for more information. /// UnixData = 0x00000010, /// /// Do not capture security descriptors. /// Only has an effect in NTFS-3G capture mode, or in Windows native builds. /// NoAcls = 0x00000020, /// /// Fail immediately if the full security descriptor of any file or directory cannot be accessed. /// Only has an effect in Windows native builds. /// The default behavior without this flag is to first try omitting the SACL from the security descriptor, /// then to try omitting the security descriptor entirely. /// StrictAcls = 0x00000040, /// /// Call the progress function with the message when a directory or file is excluded from capture. /// This is a subset of the messages provided by . /// ExcludeVerbose = 0x00000080, /// /// Reparse-point fixups: /// Modify absolute symbolic links (and junctions, in the case of Windows) that point inside the directory /// being captured to instead be absolute relative to the directory being captured. /// /// Without this flag, the default is to do reparse-point fixups if WIM_HDR_FLAG_RP_FIX is set in the WIM header /// or if this is the first image being added. /// RpFix = 0x00000100, /// /// Don't do reparse point fixups. See . /// NoRpFix = 0x00000200, /// /// Do not automatically exclude unsupported files or directories from capture, /// such as encrypted files in NTFS-3G capture mode, or device files and FIFOs on /// UNIX-like systems when not also using . /// Instead, fail with when such a file is encountered. /// NoUnsupportedExclude = 0x00000400, /// /// Automatically select a capture configuration appropriate for capturing filesystems containing Windows operating systems. /// For example, "/pagefile.sys" and "/System Volume Information" will be excluded. /// /// When this flag is specified, the corresponding config parameter (for ) or member (for ) must be null. /// Otherwise, will be returned. /// /// Note that the default behavior ---that is, when neither nor is specified and config is null--- /// is to use no capture configuration, meaning that no files are excluded from capture. /// WinConfig = 0x00000800, /// /// Capture image as "WIMBoot compatible". /// In addition, if no capture configuration file is explicitly specified use the capture configuration file /// "$SOURCE/Windows/System32/WimBootCompress.ini" if it exists, where "$SOURCE" is the directory being captured; /// or, if a capture configuration file is explicitly specified, use it and also place it at /// "/Windows/System32/WimBootCompress.ini" in the WIM image. /// /// This flag does not, by itself, change the compression type or chunk size. /// Before writing the WIM file, you may wish to set the compression format to be the same as that used by WIMGAPI and DISM: /// /// ; /// ; /// /// However, "WIMBoot" also works with other XPRESS chunk sizes as well as LZX with 32768 byte chunks. /// /// Note: AddFlags.WIMBOOT does something different from, and independent from, AddFlags.BOOT. /// /// Since wimlib v1.8.3, also causes offline WIM-backed files to be added as the "real" files /// rather than as their reparse points, provided that their data is already present in the WIM. /// This feature can be useful when updating a backing WIM file in an "offline" state. /// WimBoot = 0x00001000, /// /// If the add command involves adding a non-directory file to a location at which there already exists /// a nondirectory file in the image, issue instead of replacing the file. /// This was the default behavior before wimlib v1.7.0. /// NoReplace = 0x00002000, /// /// Send messages to the progress function. /// /// Note: This method for file exclusions is independent from the capture configuration file mechanism. /// TestFileExclusion = 0x00004000, /// /// Since wimlib v1.9.0: create a temporary filesystem snapshot of the source directory and add the files from it. /// Currently, this option is only supported on Windows, where it uses the Volume Shadow Copy Service (VSS). /// Using this option, you can create a consistent backup of the system volume of /// a running Windows system without running into problems with locked files. /// For the VSS snapshot to be successfully created, your application must be run as an Administrator, /// and it cannot be run in WoW64 mode (i.e. if Windows is 64-bit, then your application must be 64-bit as well). /// Snapshot = 0x00008000, /// /// Since wimlib v1.9.0: permit the library to discard file paths after the initial scan. /// If the application won't use while writing the WIM archive, /// this flag can be used to allow the library to enable optimizations such as opening files by inode number rather than by path. /// Currently this only makes a difference on Windows. /// FilePathsUnneeded = 0x00010000, } #endregion #region enum ChangeFlags [Flags] public enum ChangeFlags : int { None = 0x00000000, /// /// Set or unset the "readonly" WIM header flag (WIM_HDR_FLAG_READONLY in Microsoft's documentation), /// based on the member of the info parameter. /// This is distinct from basic file permissions; this flag can be set on a WIM file that is physically writable. /// /// wimlib disallows modifying on-disk WIM files with the readonly flag set. /// However, with will override this --- /// and in fact, this is necessary to set the readonly flag persistently on an existing WIM file. /// ReadOnly = 0x00000001, /// /// Set the GUID (globally unique identifier) of the WIM file to the value specified in of the info parameter. /// Guid = 0x00000002, /// /// Change the bootable image of the WIM to the value specified in of the info parameter. /// BootIndex = 0x00000004, /// /// Change the WIM_HDR_FLAG_RP_FIX flag of the WIM file to the value specified in of the info parameter. /// This flag generally indicates whether an image in the WIM has been captured with reparse-point fixups enabled. /// wimlib also treats this flag as specifying whether to do reparse-point fixups by default /// when capturing or applying WIM images. /// RpFix = 0x00000008 } #endregion #region enum DeleteFlags [Flags] public enum DeleteFlags : uint { None = 0x00000000, /// /// Do not issue an error if the path to delete does not exist. /// Force = 0x00000001, /// /// Delete the file or directory tree recursively; if not specified, an error is issued if the path to delete is a directory. /// Recursive = 0x00000002, } #endregion #region enum ExportFlags [Flags] public enum ExportFlags : uint { None = 0x00000000, /// /// If a single image is being exported, mark it bootable in the destination WIM. /// Alternatively, if is specified as the image to export, /// the image in the source WIM (if any) that is marked as bootable is also /// marked as bootable in the destination WIM. /// Boot = 0x00000001, /// /// Give the exported image(s) no names. /// Avoids problems with image name collisions. /// NoNames = 0x00000002, /// /// Give the exported image(s) no descriptions. /// NoDescriptions = 0x00000004, /// /// This advises the library that the program is finished with the source /// WIMStruct and will not attempt to access it after the call to /// , with the exception of the call to . /// Gift = 0x00000008, /// /// Mark each exported image as WIMBoot-compatible. /// /// Note: by itself, this does change the destination WIM's compression type, nor /// does it add the file "\Windows\System32\WimBootCompress.ini" in the WIM image. /// /// /// Before writing the destination WIM, it's recommended to do something like: /// /// using (Wim wim = ...) /// { /// wim.SetOutputCompressionType(wim, CompressType.XPRESS); /// wim.SetOutputChunkSize(wim, 4096); /// wim.AddTree(image, "myconfig.ini", @"\Windows\System32\WimBootCompress.ini", AddFlags.DEFAULT); /// } /// WimBoot = 0x00000010, } #endregion #region enum ExtractFlags [Flags] public enum ExtractFlags : uint { None = 0x00000000, /// /// Extract the image directly to an NTFS volume rather than a generic directory. /// This mode is only available if wimlib was compiled with libntfs-3g support; /// if not, will be returned. /// In this mode, the extraction target will be interpreted as the path to an NTFS volume image /// (as a regular file or block device) rather than a directory. /// It will be opened using libntfs-3g, and the image will be extracted to the NTFS filesystem's root directory. /// Note: this flag cannot be used when is called with as the image, /// nor can it be used with when passed multiple paths. /// Ntfs = 0x00000001, /// /// Since wimlib v1.13.4: Don't consider corrupted files to be an error. /// Just extract them in whatever form we can. /// RecoverData = 0x00000002, /// /// UNIX-like systems only: /// Extract UNIX-specific metadata captured with . /// UnixData = 0x00000020, /// /// Do not extract security descriptors. /// This flag cannot be combined with . /// NoAcls = 0x00000040, /// /// Fail immediately if the full security descriptor of any file or directory /// cannot be set exactly as specified in the WIM image. /// On Windows, the default behavior without this flag when wimlib does not have permission to set the /// correct security descriptor is to fall back to setting the security descriptor with the SACL omitted, /// then with the DACL omitted, then with the owner omitted, then not at all. /// This flag cannot be combined with . /// StrictAcls = 0x00000080, /// /// This is the extraction equivalent to . /// This forces reparse-point fixups on, so absolute symbolic links or junction points will /// be fixed to be absolute relative to the actual extraction root. /// Reparse-point fixups are done by default for and /// if WIM_HDR_FLAG_RP_FIX is set in the WIM header. /// This flag cannot be combined with . /// RpFix = 0x00000100, /// /// Force reparse-point fixups on extraction off, regardless of the state of the WIM_HDR_FLAG_RP_FIX flag in the WIM header. /// This flag cannot be combined with . /// NoRpFix = 0x00000200, /// /// For and only: /// Extract the paths, each of which must name a regular file, to standard output. /// ToStdOut = 0x00000400, /// /// Instead of ignoring files and directories with names that cannot be represented on the current platform /// (note: Windows has more restrictions on filenames than POSIX-compliant systems), /// try to replace characters or append junk to the names so that they can be extracted in some form. /// /// Note: this flag is unlikely to have any effect when extracting a WIM image that was captured on Windows. /// ReplaceInvalidFileNames = 0x00000800, /// /// On Windows, when there exist two or more files with the same case insensitive name but different case sensitive names, /// try to extract them all by appending junk to the end of them, rather than arbitrarily extracting only one. /// /// Note: this flag is unlikely to have any effect when extracting a WIM image that was captured on Windows. /// AllCaseConflicts = 0x00001000, /// /// Do not ignore failure to set timestamps on extracted files. /// This flag currently only has an effect when extracting to a directory on UNIX-like systems. /// StrictTimestamps = 0x00002000, /// /// Do not ignore failure to set short names on extracted files. /// This flag currently only has an effect on Windows. /// StrictShortNames = 0x00004000, /// /// Do not ignore failure to extract symbolic links and junctions due to permissions problems. /// This flag currently only has an effect on Windows. /// By default, such failures are ignored since the default configuration of Windows /// only allows the Administrator to create symbolic links. /// StrictSymlinks = 0x00008000, /// /// For and only: /// Treat the paths to extract as wildcard patterns ("globs") which may contain the wildcard characters '?' and '*'. /// The '?' character matches any non-path-separator character, whereas the '*' character matches zero or more /// non-path-separator characters. /// Consequently, each glob may match zero or more actual paths in the WIM image. /// /// By default, if a glob does not match any files, a warning but not an error will be issued. /// This is the case even if the glob did not actually contain wildcard characters. /// Use to get an error instead. /// GlobPaths = 0x00040000, /// /// In combination with , causes an error () /// rather than a warning to be issued when one of the provided globs did not match a file. /// StrictGlob = 0x00080000, /// /// Do not extract Windows file attributes such as readonly, hidden, etc. /// /// This flag has an effect on Windows as well as in the NTFS-3G extraction mode. /// NoAttributes = 0x00100000, /// /// For and only: /// Do not preserve the directory structure of the archive when extracting --- that is, /// place each extracted file or directory tree directly in the target directory. /// The target directory will still be created if it does not already exist. /// NoPreserveDirStructure = 0x00200000, /// /// Windows only: Extract files as "pointers" back to the WIM archive. /// /// The effects of this option are fairly complex. /// See the documentation for the "--wimboot" option of "wimapply" for more information. /// WimBoot = 0x00400000, /// /// Since wimlib v1.8.2 and Windows-only: /// compress the extracted files using System Compression, when possible. /// This only works on either Windows 10 or later, or on an older Windows to which Microsoft's wofadk.sys driver has been added. /// Several different compression formats may be used with System Compression; /// this particular flag selects the XPRESS compression format with 4096 byte chunks. /// CompactXPRESS4K = 0x01000000, /// /// Like E, but use XPRESS compression with 8192 byte chunks. /// CompactXPRESS8K = 0x02000000, /// /// Like , but use XPRESS compression with 16384 byte chunks. /// CompactXPRESS16K = 0x04000000, /// /// Like , but use LZX compression with 32768 byte chunks. /// CompactLZX = 0x08000000, } #endregion #region enum MountFlags (Linux Only) [Flags] public enum MountFlags : uint { None = 0x00000000, /// /// Mount the WIM image read-write rather than the default of read-only. /// ReadWrite = 0x00000001, /// /// Enable FUSE debugging by passing the -d option to fuse_main(). /// Debug = 0x00000002, /// /// Do not allow accessing named data streams in the mounted WIM image. /// StreamInterfaceNone = 0x00000004, /// /// Access named data streams in the mounted WIM image through extended file /// attributes named "user.X", where X is the name of a data stream. /// This is the default mode. /// StreamInterfaceXAttr = 0x00000008, /// /// Access named data streams in the mounted WIM image by specifying the file /// name, a colon, then the name of the data stream. /// StreamInterfaceWindows = 0x00000010, /// /// Support UNIX owners, groups, modes, and special files. /// UnixData = 0x00000020, /// /// Allow other users to see the mounted filesystem. /// This passes the allow_other option to fuse_main(). /// AllowOther = 0x00000040, } #endregion #region enum OpenFlags [Flags] public enum OpenFlags : int { None = 0x00000000, /// /// Verify the WIM contents against the WIM's integrity table, if present. /// The integrity table stores checksums for the raw data of the WIM file, divided into fixed size chunks. /// Verification will compute checksums and compare them with the stored values. /// If there are any mismatches, then will be issued. /// If the WIM file does not contain an integrity table, then this flag has no effect. /// CheckIntegrity = 0x00000001, /// /// Issue an error () if the WIM is part of a split WIM. /// Software can provide this flag for convenience if it explicitly does not want to support split WIMs. /// ErrorIfSplit = 0x00000002, /// /// Check if the WIM is writable and issue an error () if it is not. /// A WIM is considered writable only if it is writable at the filesystem level, does not have the /// "WIM_HDR_FLAG_READONLY" flag set in its header, and is not part of a spanned set. /// It is not required to provide this flag before attempting to make changes to the WIM, /// but with this flag you get an error immediately rather than potentially much later, /// when is finally called. /// WriteAccess = 0x00000004, } #endregion #region enum UnmountFlags (Linux Only) [Flags] public enum UnmountFlags : uint { None = 0x00000000, /// /// Provide when committing the WIM image. /// Ignored if not also specified. /// CheckIntegrity = 0x00000001, /// /// Commit changes to the read-write mounted WIM image. /// If this flag is not specified, changes will be discarded. /// Commit = 0x00000002, /// /// Provide when committing the WIM image. /// Ignored if not also specified. /// Rebuild = 0x00000004, /// /// Provide when committing the WIM image. /// Ignored if not also specified. /// Recompress = 0x00000008, /// /// In combination with for a read-write mounted WIM image, /// forces all file descriptors to the open WIM image to be closed before committing it. /// /// /// Without or with a read-only mounted WIM image, this flag has no effect. /// Force = 0x00000010, /// /// In combination with for a read-write mounted WIM image, /// causes the modified image to be committed to the WIM file as a new, unnamed image appended to the archive. /// The original image in the WIM file will be unmodified. /// NewImage = 0x00000020, } #endregion #region enum UpdateFlags [Flags] public enum UpdateFlags : uint { None = 0x00000000, /// /// Send SendProgress = 0x00000001, } #endregion #region enum WriteFlags [Flags] public enum WriteFlags : uint { None = 0x00000000, /// /// Include an integrity table in the resulting WIM file. /// /// For 's created with , the default behavior is to /// include an integrity table if and only if one was present before. /// For 's created with CheckIntegrity = 0x00000001, /// /// Do not include an integrity table in the resulting WIM file. /// This is the default behavior, unless the was created by opening a WIM with an integrity table. /// NoCheckIntegrity = 0x00000002, /// /// Write the WIM as "pipable". /// After writing a WIM with this flag specified, images from it can be applied directly from a pipe. /// See the documentation for the "--pipable" option of "wimcapture" for more information. /// Beware: WIMs written with this flag will not be compatible with Microsoft's software. /// /// For 's created with , the default behavior is to write the WIM as pipable if and only if it was pipable before. /// For 's created with , the default behavior is to write the WIM as non-pipable. /// Pipable = 0x00000004, /// /// Do not write the WIM as "pipable". /// This is the default behavior, unless the was created by opening a pipable WIM. /// NotPipable = 0x00000008, /// /// When writing data to the WIM file, recompress it, even if the data is already available in the desired compressed form /// (for example, in a WIM file from which an image has been exported using ). /// /// can be used to recompress with a higher compression ratio for the same compression type and chunk size. /// Simply using the default compression settings may suffice for this, especially if the WIM /// file was created using another program/library that may not use as sophisticated compression algorithms. /// Or, can be called beforehand to set an even higher compression level than the default. /// /// If the WIM contains solid resources, then WriteFlags.RECOMPRESS can be used in /// combination with WriteFlags.SOLID to prevent any solid resources from being re-used. /// Otherwise, solid resources are re-used somewhat more liberally than normal compressed resources. /// /// does not cause recompression of data that would not otherwise be written. /// For example, a call to Wim.Overwrite() with WriteFlags.RECOMPRESS will not, by itself, /// cause already-existing data in the WIM file to be recompressed. /// To force the WIM file to be fully rebuilt and recompressed, combine with . /// Recompress = 0x00000010, /// /// Immediately before closing the WIM file, sync its data to disk. /// /// This flag forces the function to wait until the data is safely on disk before returning success. /// Otherwise, modern operating systems tend to cache data for some time (in some cases, 30+ seconds) /// before actually writing it to disk, even after reporting to the application that the writes have succeeded. /// /// will set this flag automatically if it decides to overwrite the WIM file via a temporary file instead of in-place. /// This is necessary on POSIX systems; it will, for example, avoid problems with delayed allocation on ext4. /// FSync = 0x00000020, /// /// For Wim.Overwrite(): /// rebuild the entire WIM file, even if it otherwise could be updated in-place by appending to it. /// Any data that existed in the original WIM file but is not actually needed by any of the remaining images will not be included. /// This can free up space left over after previous in-place modifications to the WIM file. /// /// This flag can be combined with to force all data to be recompressed. /// Otherwise, compressed data is re-used if possible. /// /// ignores this flag. /// Rebuild = 0x00000040, /// /// For Wim.Overwrite(): /// override the default behavior after one or more calls to , which is to rebuild the entire WIM file. /// With this flag, only minimal changes to correctly remove the image from the WIM file will be taken. /// This can be much faster, but it will result in the WIM file getting larger rather than smaller. /// /// ignores this flag. /// SoftDelete = 0x00000080, /// /// For , allow overwriting the WIM file even if the readonly flag (WIM_HDR_FLAG_READONLY) is set in the WIM header. /// This can be used following a call to with the flag to /// actually set the readonly flag on the on-disk WIM file. /// /// ignores this flag. /// IgnoreReadOnlyFlag = 0x00000100, /// /// Do not include file data already present in other WIMs. /// This flag can be used to write a "delta" WIM after the WIM files on which the delta is to be /// based were referenced with /// or . /// SkipExternalWims = 0x00000200, /// /// For , retain the WIM's GUID instead of generating a new one. /// /// sets this by default, since the WIM remains, logically, the same file. /// RetainGuid = 0x00000800, /// /// Concatenate files and compress them together, rather than compress each file independently. /// This is also known as creating a "solid archive". /// This tends to produce a better compression ratio at the cost of much slower random access. /// /// WIM files created with this flag are only compatible with wimlib v1.6.0 or later, /// WIMGAPI Windows 8 or later, and DISM Windows 8.1 or later. /// WIM files created with this flag use a different version number in their header /// (3584 instead of 68864) and are also called "ESD files". /// /// Note that providing this flag does not affect the "append by default" behavior of . /// In other words, with just can be used to append solid-compressed data to a /// WIM file that originally did not contain any solid-compressed data. /// But if you instead want to rebuild and recompress an entire WIM file in solid mode, /// then also provide and . /// /// Currently, new solid resources will, by default, be written using LZMS compression with 64 MiB (67108864 byte) chunks. /// Use and/or to change this. /// This is independent of the WIM's main compression type and chunk size; /// you can have a WIM that nominally uses LZX compression and 32768 byte chunks but actually contains /// LZMS-compressed solid resources, for example. /// However, if including solid resources, I suggest that you set the WIM's main compression type to LZMS as well, /// either by creating the WIM with /// or by calling . /// /// This flag will be set by default when writing or overwriting a WIM file that /// either already contains solid resources, or has had solid resources exported /// into it and the WIM's main compression type is LZMS. /// Solid = 0x00001000, /// /// Send messages while writing the WIM file. /// This is only needed in the unusual case that the library user needs to /// know exactly when wimlib has read each file for the last time. /// SendDoneWithFileMessages = 0x00002000, /// /// Do not consider content similarity when arranging file data for solid compression. /// Providing this flag will typically worsen the compression ratio, /// so only provide this flag if you know what you are doing. /// NoSolidSort = 0x00004000, /// /// Since wimlib v1.8.3 and for only: "unsafely" compact the WIM file in-place, without appending. /// Existing resources are shifted down to fill holes and new resources are appended as needed. /// The WIM file is truncated to its final size, which may shrink the on-disk file. /// /// This operation cannot be safely interrupted. /// If the operation is interrupted, then the WIM file will be corrupted, /// and it may be impossible (or at least very difficult) to recover any data from it. /// Users of this flag are expected to know what they are doing and assume responsibility for any data corruption that may result. /// /// If the WIM file cannot be compacted in-place because of its structure, its layout, or other requested write parameters, /// then fails with , /// and the caller may wish to retry the operation without this flag. /// UnsafeCompact = 0x00008000, } #endregion #region enum InitFlags [Flags] public enum InitFlags : uint { None = 0x00000000, /// /// Windows-only: /// Do not attempt to acquire additional privileges (currently SeBackupPrivilege, SeRestorePrivilege, /// SeSecurityPrivilege, SeTakeOwnershipPrivilege, and SeManageVolumePrivilege) when initializing the library. /// /// This flag is intended for the case where the calling program manages these privileges itself. /// Note: by default, no error is issued if privileges cannot be acquired, although related errors may be reported later, /// depending on if the operations performed actually require additional privileges or not. /// DontAcquirePrivileges = 0x00000002, /// /// Windows only: /// If not specified, return if privileges /// that may be needed to read all possible data and metadata for a capture operation could not be acquired. /// Can be combined with . /// StrictCapturePrivileges = 0x00000004, /// /// Windows only: /// If not specified, return if privileges /// that may be needed to restore all possible data and metadata for an apply operation could not be acquired. /// Can be combined with . /// StrictApplyPrivileges = 0x00000008, /// /// Default to interpreting WIM paths case sensitively (default on UNIX-like systems). /// DefaultCaseSensitive = 0x00000010, /// /// Default to interpreting WIM paths case insensitively (default on Windows). /// This does not apply to mounted images. /// DefaultCaseInsensitive = 0x00000020, } #endregion #region enum RefFlags [Flags] public enum RefFlags : int { None = 0x00000000, /// /// For , enable shell-style filename globbing. /// Ignored by . /// GlobEnable = 0x00000001, /// /// For , issue an error () if a glob did not match any files. /// The default behavior without this flag is to issue no error at that point, but then attempt to open /// the glob as a literal path, which of course will fail anyway if no file exists at that path. /// No effect if is not also specified. /// Ignored by . /// GlobErrOnNoMatch = 0x00000002, } #endregion #region enum CompressorFlags [Flags] public enum CompressorFlags : uint { None = 0x0, Destructive = 0x80000000, } #endregion #endregion #region Native wimlib structures #region WimInfo [StructLayout(LayoutKind.Sequential)] public class WimInfo { /// /// The globally unique identifier for this WIM. /// (Note: all parts of a split WIM normally have identical GUIDs.) /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] Guid; /// /// The number of images in this WIM file. /// public uint ImageCount; /// /// The 1-based index of the bootable image in this WIM file, or 0 if no image is bootable. /// public uint BootIndex; /// /// The version of the WIM file format used in this WIM file. /// public uint WimVersion; /// /// The default compression chunk size of resources in this WIM file. /// public uint ChunkSize; /// /// For split WIMs, the 1-based index of this part within the split WIM; otherwise 1. /// public ushort PartNumber; /// /// For split WIMs, the total number of parts in the split WIM; otherwise 1. /// public ushort TotalParts; /// /// The default compression type of resources in this WIM file, as enum. /// public CompressionType CompressionType { get => (CompressionType)CompressionTypeInt; set => CompressionTypeInt = (int)value; } private int CompressionTypeInt; /// /// The size of this WIM file in bytes, excluding the XML data and integrity table. /// public ulong TotalBytes; /// /// Bit 0 - 9 : Information Flags /// Bit 10 - 31 : Reserved /// private uint _bitFlag; /// /// 1 iff this WIM file has an integrity table. /// public bool HasIntegrityTable { get => WimLibLoader.GetBitField(_bitFlag, 0); set => WimLibLoader.SetBitField(ref _bitFlag, 0, value); } /// /// 1 iff this info struct is for a that has a backing file. /// public bool OpenedFromFile { get => WimLibLoader.GetBitField(_bitFlag, 1); set => WimLibLoader.SetBitField(ref _bitFlag, 1, value); } /// /// 1 iff this WIM file is considered readonly for any reason /// (e.g. the "readonly" header flag is set, or this is part of a split WIM, or filesystem permissions deny writing) /// public bool IsReadOnly { get => WimLibLoader.GetBitField(_bitFlag, 2); set => WimLibLoader.SetBitField(ref _bitFlag, 2, value); } /// /// 1 iff the "reparse point fix" flag is set in this WIM's header /// public bool HasRpfix { get => WimLibLoader.GetBitField(_bitFlag, 3); set => WimLibLoader.SetBitField(ref _bitFlag, 3, value); } /// /// 1 iff the "readonly" flag is set in this WIM's header /// public bool IsMarkedReadOnly { get => WimLibLoader.GetBitField(_bitFlag, 4); set => WimLibLoader.SetBitField(ref _bitFlag, 4, value); } /// /// 1 iff the "spanned" flag is set in this WIM's header /// public bool Spanned { get => WimLibLoader.GetBitField(_bitFlag, 5); set => WimLibLoader.SetBitField(ref _bitFlag, 5, value); } /// /// 1 iff the "write in progress" flag is set in this WIM's header /// public bool WriteInProgress { get => WimLibLoader.GetBitField(_bitFlag, 6); set => WimLibLoader.SetBitField(ref _bitFlag, 6, value); } /// /// 1 iff the "metadata only" flag is set in this WIM's header /// public bool MetadataOnly { get => WimLibLoader.GetBitField(_bitFlag, 7); set => WimLibLoader.SetBitField(ref _bitFlag, 7, value); } /// /// 1 iff the "resource only" flag is set in this WIM's header /// public bool ResourceOnly { get => WimLibLoader.GetBitField(_bitFlag, 8); set => WimLibLoader.SetBitField(ref _bitFlag, 8, value); } /// /// 1 iff this WIM file is pipable (see WriteFlags.PIPABLE). /// public bool Pipable { get => WimLibLoader.GetBitField(_bitFlag, 9); set => WimLibLoader.SetBitField(ref _bitFlag, 9, value); } [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] private uint[] _reserved; } #endregion #region CaptureSource /// /// An array of these structures is passed to /// to specify the sources from which to create a WIM image. /// /// /// Wrapper class of Utf{16,8}.CaptureSourceBase. /// Instance of the class is converted to on-the-fly. /// public class CaptureSource { /// /// Absolute or relative path to a file or directory on the external filesystem to be included in the image. /// public string FsSourcePath; /// /// Destination path in the image. /// To specify the root directory of the image, use @"\". /// public string WimTargetPath; public CaptureSource(string fsSourcePath, string wimTargetPath) { FsSourcePath = fsSourcePath; WimTargetPath = wimTargetPath; } }; #region UpdateCommand public class UpdateCommand { #region Field - UpdateCommandOp public UpdateOp Op; #endregion #region Field - Related to Add /// /// Filesystem path to the file or directory tree to add. /// public string AddFsSourcePath; /// /// Destination path in the image. /// To specify the root directory of the image, use Wim.RootPath. /// public string AddWimTargetPath; /// /// Path to capture configuration file to use, or null if not specified. /// public string AddConfigFile; /// /// Bitwise OR of AddFlags. /// public AddFlags AddFlags; #endregion #region Field - Related to Delete /// /// The path to the file or directory within the image to delete. /// public string DelWimPath; /// /// Bitwise OR of DeleteFlags. /// public DeleteFlags DeleteFlags; #endregion #region Field - Related to Rename /// /// The path to the source file or directory within the image. /// public string RenWimSourcePath; /// /// The path to the destination file or directory within the image. /// public string RenWimTargetPath; /// /// Reserved; set to 0. /// private int _renameFlags; #endregion #region Properties - Add, Delete, Rename public AddCommand Add { get { if (Op != UpdateOp.Add) throw new InvalidOperationException("Field [Op] should be [UpdateOp.ADD]"); return new AddCommand(AddFsSourcePath, AddWimTargetPath, AddConfigFile, AddFlags); } set { Op = UpdateOp.Add; AddFsSourcePath = value.FsSourcePath; AddWimTargetPath = value.WimTargetPath; AddConfigFile = value.ConfigFile; AddFlags = value.AddFlags; } } public DeleteCommand Delete { get { if (Op != UpdateOp.Delete) throw new InvalidOperationException("Field [Op] should be [UpdateOp.DELETE]"); return new DeleteCommand(DelWimPath, DeleteFlags); } set { Op = UpdateOp.Delete; DelWimPath = value.WimPath; DeleteFlags = value.DeleteFlags; } } public RenameCommand Rename { get { if (Op != UpdateOp.Rename) throw new InvalidOperationException("Field [Op] should be [UpdateOp.DELETE]"); return new RenameCommand(RenWimSourcePath, RenWimTargetPath); } set { Op = UpdateOp.Rename; RenWimSourcePath = value.WimSourcePath; RenWimTargetPath = value.WimTargetPath; _renameFlags = 0; } } #endregion #region Factory Methods public static UpdateCommand SetAdd(string fsSourcePath, string wimTargetPath, string configFile, AddFlags addFlags) { return new UpdateCommand { Op = UpdateOp.Add, AddFsSourcePath = fsSourcePath, AddWimTargetPath = wimTargetPath, AddConfigFile = configFile, AddFlags = addFlags, }; } public static UpdateCommand SetAdd(AddCommand add) { return new UpdateCommand { Op = UpdateOp.Add, AddFsSourcePath = add.FsSourcePath, AddWimTargetPath = add.WimTargetPath, AddConfigFile = add.ConfigFile, AddFlags = add.AddFlags, }; } public static UpdateCommand SetDelete(string wimPath, DeleteFlags deleteFlags) { return new UpdateCommand { Op = UpdateOp.Delete, DelWimPath = wimPath, DeleteFlags = deleteFlags, }; } public static UpdateCommand SetDelete(DeleteCommand del) { return new UpdateCommand { Op = UpdateOp.Delete, DelWimPath = del.WimPath, DeleteFlags = del.DeleteFlags, }; } public static UpdateCommand SetRename(string wimSourcePath, string wimTargetPath) { return new UpdateCommand { Op = UpdateOp.Rename, RenWimSourcePath = wimSourcePath, RenWimTargetPath = wimTargetPath, _renameFlags = 0, }; } public static UpdateCommand SetRename(RenameCommand ren) { return new UpdateCommand { Op = UpdateOp.Rename, RenWimSourcePath = ren.WimSourcePath, RenWimTargetPath = ren.WimTargetPath, _renameFlags = 0, }; } #endregion #region ToNativeStruct internal UpdateCommand32 ToNativeStruct32() { return Op switch { UpdateOp.Add => new UpdateCommand32 { Op = UpdateOp.Add, AddFsSourcePath = AddFsSourcePath, AddWimTargetPath = AddWimTargetPath, AddConfigFile = AddConfigFile, AddFlags = AddFlags, }, UpdateOp.Delete => new UpdateCommand32 { Op = UpdateOp.Delete, DelWimPath = DelWimPath, DeleteFlags = DeleteFlags, }, UpdateOp.Rename => new UpdateCommand32 { Op = UpdateOp.Rename, RenWimSourcePath = RenWimSourcePath, RenWimTargetPath = RenWimTargetPath, }, _ => throw new InvalidOperationException("Internal Logic Error at UpdateCommand.ToNativeStruct32()"), }; } internal UpdateCommand64 ToNativeStruct64() { return Op switch { UpdateOp.Add => new UpdateCommand64 { Op = UpdateOp.Add, AddFsSourcePath = AddFsSourcePath, AddWimTargetPath = AddWimTargetPath, AddConfigFile = AddConfigFile, AddFlags = AddFlags, }, UpdateOp.Delete => new UpdateCommand64 { Op = UpdateOp.Delete, DelWimPath = DelWimPath, DeleteFlags = DeleteFlags, }, UpdateOp.Rename => new UpdateCommand64 { Op = UpdateOp.Rename, RenWimSourcePath = RenWimSourcePath, RenWimTargetPath = RenWimTargetPath, }, _ => throw new InvalidOperationException("Internal Logic Error at UpdateCommand.ToNativeStruct64()"), }; } #endregion } #endregion #region enum UpdateOp [Flags] public enum UpdateOp : uint { /// /// Add a new file or directory tree to the image. /// Add = 0, /// /// Delete a file or directory tree from the image. /// Delete = 1, /// /// Rename a file or directory tree in the image. /// Rename = 2, } #endregion #region AddCommand, DeleteCommand, RenameCommand public class AddCommand { /// /// Filesystem path to the file or directory tree to add. /// public string FsSourcePath; /// /// Destination path in the image. /// To specify the root directory of the image, use . /// public string WimTargetPath; /// /// Path to capture configuration file to use, or null if not specified. /// public string ConfigFile; /// /// Bitwise OR of AddFlags. /// public AddFlags AddFlags; public AddCommand(string fsSourcePath, string wimTargetPath, string configFile, AddFlags addFlags) { FsSourcePath = fsSourcePath; WimTargetPath = wimTargetPath; ConfigFile = configFile; AddFlags = addFlags; } } public class DeleteCommand { /// /// The path to the file or directory within the image to delete. /// public string WimPath; /// /// Bitwise OR of DeleteFlags. /// public DeleteFlags DeleteFlags; public DeleteCommand(string wimPath, DeleteFlags deleteFlags) { WimPath = wimPath; DeleteFlags = deleteFlags; } } public class RenameCommand { /// /// The path to the source file or directory within the image. /// public string WimSourcePath; /// /// The path to the destination file or directory within the image. /// public string WimTargetPath; public RenameCommand(string wimSourcePath, string wimTargetPath) { WimSourcePath = wimSourcePath; WimTargetPath = wimTargetPath; } } #endregion #region struct UpdateCommand32 /// /// 32bit version of real UpdateCommand struct which is passed to the native wimlib. /// Must be struct (using class crashes the process) /// /// /// Original C struct (wimlib_update_command) contains a union with three structs: Add, Delete and Rename. /// LayoutKind.Explicit is required to represent them in the .Net world. /// C# struct was used instead of class because .Net have to pass an array of value type to C code. /// [StructLayout(LayoutKind.Explicit)] internal struct UpdateCommand32 { [FieldOffset(0)] public UpdateOp Op; #region AddCommand /// /// Filesystem path to the file or directory tree to add. /// [FieldOffset(4)] private IntPtr _addFsSourcePathPtr; public string AddFsSourcePath { get => Wim.Lib.PtrToStringAuto(_addFsSourcePathPtr); set => UpdatePtr(ref _addFsSourcePathPtr, value); } /// /// Destination path in the image. To specify the root directory of the image, use . /// [FieldOffset(8)] private IntPtr _addWimTargetPathPtr; public string AddWimTargetPath { get => Wim.Lib.PtrToStringAuto(_addWimTargetPathPtr); set => UpdatePtr(ref _addWimTargetPathPtr, value); } /// /// Path to capture configuration file to use, or null if not specified. /// [FieldOffset(12)] private IntPtr _addConfigFilePtr; public string AddConfigFile { get => Wim.Lib.PtrToStringAuto(_addConfigFilePtr); set => UpdatePtr(ref _addConfigFilePtr, value); } /// /// Bitwise OR of AddFlags. /// [FieldOffset(16)] public AddFlags AddFlags; #endregion #region DeleteCommand /// /// The path to the file or directory within the image to delete. /// [FieldOffset(4)] private IntPtr _delWimPathPtr; public string DelWimPath { get => Wim.Lib.PtrToStringAuto(_delWimPathPtr); set => UpdatePtr(ref _delWimPathPtr, value); } /// /// Bitwise OR of DeleteFlags. /// [FieldOffset(8)] public DeleteFlags DeleteFlags; #endregion #region RenameCommand /// /// The path to the source file or directory within the image. /// [FieldOffset(4)] private IntPtr _renWimSourcePathPtr; public string RenWimSourcePath { get => Wim.Lib.PtrToStringAuto(_renWimSourcePathPtr); set => UpdatePtr(ref _renWimSourcePathPtr, value); } /// /// The path to the destination file or directory within the image. /// [FieldOffset(8)] private IntPtr _renWimTargetPathPtr; public string RenWimTargetPath { get => Wim.Lib.PtrToStringAuto(_renWimTargetPathPtr); set => UpdatePtr(ref _renWimTargetPathPtr, value); } /// /// Reserved; set to 0. /// [FieldOffset(12)] private int _renameFlags; #endregion #region Free public void Free() { switch (Op) { case UpdateOp.Add: FreePtr(ref _addFsSourcePathPtr); FreePtr(ref _addWimTargetPathPtr); FreePtr(ref _addConfigFilePtr); break; case UpdateOp.Delete: FreePtr(ref _delWimPathPtr); break; case UpdateOp.Rename: FreePtr(ref _renWimSourcePathPtr); FreePtr(ref _renWimTargetPathPtr); break; } } internal static void FreePtr(ref IntPtr ptr) { if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr); ptr = IntPtr.Zero; } internal static void UpdatePtr(ref IntPtr ptr, string str) { FreePtr(ref ptr); ptr = Wim.Lib.StringToHGlobalAuto(str); } #endregion #region ToManagedClass public UpdateCommand ToManagedClass() { return Op switch { UpdateOp.Add => UpdateCommand.SetAdd(AddFsSourcePath, AddWimTargetPath, AddConfigFile, AddFlags), UpdateOp.Delete => UpdateCommand.SetDelete(DelWimPath, DeleteFlags), UpdateOp.Rename => UpdateCommand.SetRename(RenWimSourcePath, RenWimTargetPath), _ => throw new InvalidOperationException("Internal Logic Error at UpdateCommand32.Convert()"), }; } #endregion } #endregion #region struct UpdateCommand64 /// /// 64bit version of real UpdateCommand struct which is passed to the native wimlib. /// Must be struct (using class crashes the process) /// /// /// Original C struct (wimlib_update_command) contains a union with three structs: Add, Delete and Rename. /// LayoutKind.Explicit is required to represent them in the .Net world. /// C# struct was used instead of class because .Net have to pass an array of value type to C code. /// [StructLayout(LayoutKind.Explicit)] internal struct UpdateCommand64 { [FieldOffset(0)] public UpdateOp Op; #region AddCommand /// /// Filesystem path to the file or directory tree to add. /// [FieldOffset(8)] private IntPtr _addFsSourcePathPtr; public string AddFsSourcePath { get => Wim.Lib.PtrToStringAuto(_addFsSourcePathPtr); set => UpdatePtr(ref _addFsSourcePathPtr, value); } /// /// Destination path in the image. To specify the root directory of the image, use . /// [FieldOffset(16)] private IntPtr _addWimTargetPathPtr; public string AddWimTargetPath { get => Wim.Lib.PtrToStringAuto(_addWimTargetPathPtr); set => UpdatePtr(ref _addWimTargetPathPtr, value); } /// /// Path to capture configuration file to use, or null if not specified. /// [FieldOffset(24)] private IntPtr _addConfigFilePtr; public string AddConfigFile { get => Wim.Lib.PtrToStringAuto(_addConfigFilePtr); set => UpdatePtr(ref _addConfigFilePtr, value); } /// /// Bitwise OR of AddFlags. /// [FieldOffset(32)] public AddFlags AddFlags; #endregion #region DeleteCommand /// /// The path to the file or directory within the image to delete. /// [FieldOffset(8)] private IntPtr _delWimPathPtr; public string DelWimPath { get => Wim.Lib.PtrToStringAuto(_delWimPathPtr); set => UpdatePtr(ref _delWimPathPtr, value); } /// /// Bitwise OR of DeleteFlags. /// [FieldOffset(16)] public DeleteFlags DeleteFlags; #endregion #region RenameCommand /// /// The path to the source file or directory within the image. /// [FieldOffset(8)] private IntPtr _renWimSourcePathPtr; public string RenWimSourcePath { get => Wim.Lib.PtrToStringAuto(_renWimSourcePathPtr); set => UpdatePtr(ref _renWimSourcePathPtr, value); } /// /// The path to the destination file or directory within the image. /// [FieldOffset(16)] private IntPtr _renWimTargetPathPtr; public string RenWimTargetPath { get => Wim.Lib.PtrToStringAuto(_renWimTargetPathPtr); set => UpdatePtr(ref _renWimTargetPathPtr, value); } /// /// Reserved; set to 0. /// [FieldOffset(24)] private int _renameFlags; #endregion #region Free public void Free() { switch (Op) { case UpdateOp.Add: FreePtr(ref _addFsSourcePathPtr); FreePtr(ref _addWimTargetPathPtr); FreePtr(ref _addConfigFilePtr); break; case UpdateOp.Delete: FreePtr(ref _delWimPathPtr); break; case UpdateOp.Rename: FreePtr(ref _renWimSourcePathPtr); FreePtr(ref _renWimTargetPathPtr); break; } } internal static void FreePtr(ref IntPtr ptr) { if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr); ptr = IntPtr.Zero; } internal static void UpdatePtr(ref IntPtr ptr, string str) { FreePtr(ref ptr); ptr = Wim.Lib.StringToHGlobalAuto(str); } #endregion #region ToManagedClass public UpdateCommand ToManagedClass() { return Op switch { UpdateOp.Add => UpdateCommand.SetAdd(AddFsSourcePath, AddWimTargetPath, AddConfigFile, AddFlags), UpdateOp.Delete => UpdateCommand.SetDelete(DelWimPath, DeleteFlags), UpdateOp.Rename => UpdateCommand.SetRename(RenWimSourcePath, RenWimTargetPath), _ => throw new InvalidOperationException("Internal Logic Error at UpdateCommand64.Convert()"), }; } #endregion } #endregion #endregion #region DirEntry /// /// Structure passed to the callback function. /// Roughly, the information about a "file" in the WIM image --- but really a directory entry ("dentry") because hard links are allowed. /// The field can be used to distinguish actual file inodes. /// [StructLayout(LayoutKind.Sequential)] internal class DirEntryBase { /// /// Name of the file, or null if this file is unnamed. Only the root directory of an image will be unnamed. /// public string FileName => Wim.Lib.PtrToStringAuto(_fileNamePtr); private IntPtr _fileNamePtr; /// /// 8.3 name (or "DOS name", or "short name") of this file; or null if this file has no such name. /// public string DosName => Wim.Lib.PtrToStringAuto(_dosNamePtr); private IntPtr _dosNamePtr; /// /// Full path to this file within the image. /// Path separators will be . /// public string FullPath => Wim.Lib.PtrToStringAuto(_fullPathPtr); private IntPtr _fullPathPtr; /// /// Depth of this directory entry, where 0 is the root, 1 is the root's children, ..., etc. /// public ulong Depth => DepthVal.ToUInt64(); private UIntPtr DepthVal; // size_t /// /// Pointer to the security descriptor for this file, in Windows SECURITY_DESCRIPTOR_RELATIVE format, /// or null if this file has no security descriptor. /// public byte[] SecurityDescriptor { get { if (SecurityDescriptorPtr == IntPtr.Zero) return null; byte[] buf = new byte[SecurityDescriptorSize]; Marshal.Copy(SecurityDescriptorPtr, buf, 0, (int)_securityDescriptorSizeVal.ToUInt32()); return buf; } } public IntPtr SecurityDescriptorPtr; /// /// Size of the above security descriptor, in bytes. /// public ulong SecurityDescriptorSize => _securityDescriptorSizeVal.ToUInt64(); private UIntPtr _securityDescriptorSizeVal; // size_t /// /// File attributes, such as whether the file is a directory or not. /// These are the "standard" Windows FILE_ATTRIBUTE_* values, thus typed. /// public FileAttributes Attributes; /// /// If the file is a reparse point (FileAttribute.REPARSE_POINT set in the attributes), this will give the reparse tag. /// This tells you whether the reparse point is a symbolic link, junction point, or some other, more unusual kind of reparse point. /// public ReparseTag ReparseTag; /// /// Number of links to this file's inode (hard links). /// /// Currently, this will always be 1 for directories. /// However, it can be greater than 1 for nondirectory files. /// public uint NumLinks; /// /// Number of named data streams this file has. /// Normally 0. /// public uint NumNamedStreams; /// /// A unique identifier for this file's inode. /// However, as a special case, if the inode only has a single link (NumLinks == 1), this value may be 0. /// /// Note: if a WIM image is captured from a filesystem, this value is not /// guaranteed to be the same as the original number of the inode on the filesystem. /// public ulong HardLinkGroupId; /// /// Time this file was created. /// public DateTime CreationTime => _creationTimeVal.ToDateTime(_creationTimeHigh); private WimTimeSpec _creationTimeVal; /// /// Time this file was last written to. /// public DateTime LastWriteTime => _lastWriteTimeVal.ToDateTime(_lastWriteTimeHigh); private WimTimeSpec _lastWriteTimeVal; /// /// Time this file was last accessed. /// public DateTime LastAccessTime => _lastAccessTimeVal.ToDateTime(_lastAccessTimeHigh); private WimTimeSpec _lastAccessTimeVal; /// /// The UNIX user ID of this file. /// This is a wimlib extension. /// /// This field is only valid if UnixMode != 0. /// public uint UnixUserId; /// /// The UNIX group ID of this file. /// This is a wimlib extension. /// /// This field is only valid if UnixMode != 0. /// public uint UnixGroupId; /// /// The UNIX mode of this file. /// This is a wimlib extension. /// /// If this field is 0, then UnixUid, UnixGid, UnixMode, and UnixRootDevice are all unknown /// (fields are not present in the WIM/ image). /// public uint UnixMode; /// /// The UNIX device ID (major and minor number) of this file. /// This is a wimlib extension. /// /// This field is only valid if UnixMode != 0. /// public uint UnixRootDevice; /// /// The object ID of this file, if any. /// Only valid if WimObjectId.ObjectId is not all zeroes. /// public WimObjectId ObjectId; private int _creationTimeHigh; private int _lastWriteTimeHigh; private int _lastAccessTimeHigh; private int _reserved2; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] private ulong[] _reserved; } /// /// Structure passed to the callback function. /// Roughly, the information about a "file" in the WIM image --- but really a directory entry ("dentry") because hard links are allowed. /// The field can be used to distinguish actual file inodes. /// /// /// Wrapper of , created to handle array of variable-length type . /// Converted to and from on-the-fly. /// public class DirEntry { /// /// Name of the file, or null if this file is unnamed. Only the root directory of an image will be unnamed. /// public string FileName; /// /// 8.3 name (or "DOS name", or "short name") of this file; or null if this file has no such name. /// public string DosName; /// /// Full path to this file within the image. /// Path separators will be . /// public string FullPath; /// /// Depth of this directory entry, where 0 is the root, 1 is the root's children, ..., etc. /// public ulong Depth; /// /// A security descriptor for this file, in Windows SECURITY_DESCRIPTOR_RELATIVE format, /// or null if this file has no security descriptor. /// public byte[] SecurityDescriptor; /// /// File attributes, such as whether the file is a directory or not. /// These are the "standard" Windows FILE_ATTRIBUTE_* values, thus typed. /// public FileAttributes Attributes; /// /// If the file is a reparse point (FileAttribute.REPARSE_POINT set in the attributes), this will give the reparse tag. /// This tells you whether the reparse point is a symbolic link, junction point, or some other, more unusual kind of reparse point. /// public ReparseTag ReparseTag; /// /// Number of links to this file's inode (hard links). /// /// Currently, this will always be 1 for directories. /// However, it can be greater than 1 for nondirectory files. /// public uint NumLinks; /// /// Number of named data streams this file has. /// Normally 0. /// public uint NumNamedStreams; /// /// A unique identifier for this file's inode. /// However, as a special case, if the inode only has a single link (NumLinks == 1), this value may be 0. /// /// Note: if a WIM image is captured from a filesystem, this value is not /// guaranteed to be the same as the original number of the inode on the filesystem. /// public ulong HardLinkGroupId; /// /// Time this file was created. /// /// /// Time this file was created. /// public DateTime CreationTime; /// /// Time this file was last written to. /// public DateTime LastWriteTime; /// /// Time this file was last accessed. /// public DateTime LastAccessTime; /// /// The UNIX user ID of this file. /// This is a wimlib extension. /// /// This field is only valid if UnixMode != 0. /// public uint UnixUserId; /// /// The UNIX group ID of this file. /// This is a wimlib extension. /// /// This field is only valid if UnixMode != 0. /// public uint UnixGroupId; /// /// The UNIX mode of this file. /// This is a wimlib extension. /// /// If this field is 0, then UnixUid, UnixGid, UnixMode, and UnixRootDevice are all unknown /// (fields are not present in the WIM/ image). /// public uint UnixMode; /// /// The UNIX device ID (major and minor number) of this file. /// This is a wimlib extension. /// /// This field is only valid if UnixMode != 0. /// public uint UnixRootDevice; /// /// The object ID of this file, if any. /// Only valid if WimObjectId.ObjectId is not all zeroes. /// public WimObjectId ObjectId; /// /// Variable-length array of streams that make up this file. /// /// The first entry will always exist and will correspond to the unnamed data stream (default file contents), /// so it will have (stream_name == null). /// Alternatively, for reparse point files, the first entry will correspond to the reparse data stream. /// Alternatively, for encrypted files, the first entry will correspond to the encrypted data. /// /// Then, following the first entry, there be NumNamedStreams additional entries that specify the named data streams, /// if any, each of which will have (stream_name != null). /// public StreamEntry[] Streams; } /// /// Refer to . /// public enum ReparseTag : uint { ReservedZero = 0x00000000, ReservedOne = 0x00000001, MountPoint = 0xA0000003, HSM = 0xC0000004, HSM2 = 0x80000006, DriverExtender = 0x80000005, SIS = 0x80000007, DFS = 0x8000000A, DFSR = 0x80000012, FilterManager = 0x8000000B, WOF = 0x80000017, SymLink = 0xA000000C, } #endregion #region struct WimTimeSpec [StructLayout(LayoutKind.Sequential)] internal struct WimTimeSpec { /// /// Represents a data, not an address. /// Represented type is int64_t in 64bit, int32_t in 32bit. /// private IntPtr _unixEpochVal; /// /// Seconds since start of UNIX epoch (January 1, 1970) /// public long UnixEpoch => _unixEpochVal.ToInt64(); /// /// Nanoseconds (0-999999999) /// public int NanoSeconds; internal DateTime ToDateTime(int high) { // C# DateTime has a resolution of 100ns DateTime genesis = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); genesis = genesis.AddSeconds(UnixEpoch); genesis = genesis.AddTicks(NanoSeconds / 100); // wimlib provide high 32bit separately if timespec.tv_sec is only 32bit if (Wim.Lib.PlatformBitness == PlatformBitness.Bit32) { long high64 = (long)high << 32; genesis = genesis.AddSeconds(high64); } return genesis; } } #endregion #region WimObjectId /// /// Since wimlib v1.9.1: an object ID, which is an extra piece of metadata that may be associated with a file on NTFS filesystems. /// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363997(v=vs.85).aspx /// [StructLayout(LayoutKind.Sequential)] public class WimObjectId { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] ObjectId; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] BirthVolumeId; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] BirthObjectId; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] DomainId; } #endregion #region ResourceEntry /// /// Information about a "blob", which is a fixed length sequence of binary data. /// Each nonempty stream of each file in a WIM image is associated with a blob. /// Blobs are deduplicated within a WIM file. /// /// /// TODO: this struct needs to be renamed, and perhaps made into a union since /// there are several cases. I'll try to list them below: /// /// 1. The blob is "missing", meaning that it is referenced by hash but not /// actually present in the WIM file. In this case we only know the /// sha1_hash. This case can only occur with , /// never . /// /// 2. Otherwise we know the uncompressed_size, the reference_count, and the /// is_metadata flag. In addition: /// /// A. If the blob is located in a non-solid WIM resource, then we also know /// the , , and . /// /// B. If the blob is located in a solid WIM resource, then we also know the /// , , , /// , . /// But the ", then /// we usually won't know any more information. The might be /// known, and prior to wimlib v1.13.6 it always was; however, in wimlib /// v1.13.6 and later, the might not be known in this case. /// /// Unknown or irrelevant fields are left zeroed. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class ResourceEntry { /// /// If this blob is not missing, then this is the uncompressed size of this blob in bytes. /// public ulong UncompressedSize; /// /// If this blob is located in a non-solid WIM resource, then this is the compressed size of that resource. /// public ulong CompressedSize; /// /// If this blob is located in a non-solid WIM resource, then this is the offset of that resource within the WIM file containing it. /// If this blob is located in a solid WIM resource, then this is the offset of this blob within that solid resource when uncompressed. /// public ulong Offset; /// /// If this blob is located in a WIM resource, then this is the SHA-1 message digest of the blob's uncompressed contents. /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] SHA1; /// /// If this blob is located in a WIM resource, then this is the part number of the WIM file containing it. /// public uint PartNumber; /// /// If this blob is not missing, then this is the number of times this blob is referenced over all images in the WIM. /// This number is not guaranteed to be correct. /// public uint ReferenceCount; /// /// Bit 0 - 6 : Bool Flags /// Bit 7 - 31 : Reserved /// private uint _bitFlag; /// /// 1 iff this blob is located in a non-solid compressed WIM resource. /// public bool IsCompressed => WimLibLoader.GetBitField(_bitFlag, 0); /// /// 1 iff this blob contains the metadata for an image. /// public bool IsMetadata => WimLibLoader.GetBitField(_bitFlag, 1); public bool IsFree => WimLibLoader.GetBitField(_bitFlag, 2); public bool IsSpanned => WimLibLoader.GetBitField(_bitFlag, 3); /// /// 1 iff a blob with this hash was not found in the blob lookup table of the . /// This normally implies a missing call to /// or . /// public bool IsMissing => WimLibLoader.GetBitField(_bitFlag, 4); /// /// 1 iff this blob is located in a solid resource. /// public bool Packed => WimLibLoader.GetBitField(_bitFlag, 5); /// /// If this blob is located in a solid WIM resource, then this is the offset of that solid resource within the WIM file containing it. /// public ulong RawResourceOffsetInWim; /// /// If this blob is located in a solid WIM resource, then this is the compressed size of that solid resource. /// public ulong RawResourceCompressedSize; /// /// If this blob is located in a solid WIM resource, then this is the uncompressed size of that solid resource. /// public ulong RawResourceUncompressedSize; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] private ulong[] _reserved; } #endregion #region StreamEntry /// /// Information about a stream of a particular file in the WIM. /// /// Normally, only WIM images captured from NTFS filesystems will have multiple streams per file. /// In practice, this is a rarely used feature of the filesystem. /// /// TODO: the library now explicitly tracks stream types,which allows it to have multiple unnamed streams /// (e.g. both a reparse point stream and unnamed data stream). /// However, this isn't yet exposed by . /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class StreamEntry { /// /// Name of the stream, or null if the stream is unnamed. /// public string StreamName; /// /// Info about this stream's data, such as its hash and size if known. /// public ResourceEntry Resource; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] private ulong[] _reserved; } #endregion #endregion } ================================================ FILE: ManagedWimLib/NugetPack.cmd ================================================ @echo off SETLOCAL ENABLEEXTENSIONS REM %~dp0 => absolute path of directory where batch file exists cd %~dp0 SET BASE=%~dp0 dotnet clean -c Release dotnet build -c Release dotnet pack -c Release -o %BASE% ENDLOCAL ================================================ FILE: ManagedWimLib/ProgressCallback.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2017-2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using Joveler.DynLoader; using System; using System.Runtime.InteropServices; // ReSharper disable FieldCanBeMadeReadOnly.Local // ReSharper disable InconsistentNaming // ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMember.Global #pragma warning disable 649 #pragma warning disable IDE0044 namespace ManagedWimLib { #region ProgressCallback delegate public delegate CallbackStatus ProgressCallback(ProgressMsg msg, object info, object progctx); #endregion #region ManagedWimLibCallback internal class ManagedProgressCallback { private readonly ProgressCallback _callback; private readonly object _userData; internal WimLibLoader.NativeProgressFunc NativeFunc { get; } public ManagedProgressCallback(ProgressCallback callback, object userData) { _callback = callback ?? throw new ArgumentNullException(nameof(callback)); _userData = userData; // Avoid GC by keeping ref here NativeFunc = NativeCallback; } private CallbackStatus NativeCallback(ProgressMsg msgType, IntPtr info, IntPtr progctx) { object pInfo = null; if (_callback == null) return CallbackStatus.Continue; switch (msgType) { case ProgressMsg.WriteStreams: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.ScanBegin: case ProgressMsg.ScanDEntry: case ProgressMsg.ScanEnd: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.ExtractSpwmPartBegin: case ProgressMsg.ExtractImageBegin: case ProgressMsg.ExtractTreeBegin: case ProgressMsg.ExtractFileStructure: case ProgressMsg.ExtractStreams: case ProgressMsg.ExtractMetadata: case ProgressMsg.ExtractTreeEnd: case ProgressMsg.ExtractImageEnd: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.Rename: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.UpdateBeginCommand: case ProgressMsg.UpdateEndCommand: UpdateProgressBase _base = Marshal.PtrToStructure(info); pInfo = _base.ToManaged(); break; case ProgressMsg.VerifyIntegrity: case ProgressMsg.CalcIntegrity: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.SplitBeginPart: case ProgressMsg.SplitEndPart: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.ReplaceFileInWim: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.WimBootExclude: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.UnmountBegin: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.DoneWithFile: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.BeginVerifyImage: case ProgressMsg.EndVerifyImage: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.VerifyStreams: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.TestFileExclusion: pInfo = Marshal.PtrToStructure(info); break; case ProgressMsg.HandleError: pInfo = Marshal.PtrToStructure(info); break; } return _callback(msgType, pInfo, _userData); } } #endregion #region ProgressInfo classes #region WriteStreamsProgress /// /// Valid on the message WRITE_STREAMS. /// This is the primary message for tracking the progress of writing a WIM file. /// [StructLayout(LayoutKind.Sequential)] public class WriteStreamsProgress { /// /// An upper bound on the number of bytes of file data that will be written. /// This number is the uncompressed size; the actual size may be lower due to compression. /// In addition, this number may decrease over time as duplicated file data is discovered. /// public ulong TotalBytes; /// /// An upper bound on the number of distinct file data "blobs" that will be written. /// This will often be similar to the "number of files", but for several reasons /// (hard links, named data streams, empty files, etc.) it can be different. /// In addition, this number may decrease over time as duplicated file data is discovered. /// public ulong TotalStreams; /// /// The number of bytes of file data that have been written so far. /// This starts at 0 and ends at TotalBytes. /// This number is the uncompressed size; the actual size may be lower due to compression. /// See for the compressed size. /// public ulong CompletedBytes; /// /// The number of distinct file data "blobs" that have been written so far. /// This starts at 0 and ends at total_streams. /// public ulong CompletedStreams; /// /// The number of threads being used for data compression; or, if no compression is being performed, this will be 1. /// public uint NumThreads; /// /// The compression type being used, as one of the CompressionType enums. /// public CompressionType CompressionType; /// /// The number of on-disk WIM files from which file data is being exported into the output WIM file. /// This can be 0, 1, or more than 1, depending on the situation. /// public uint TotalParts; /// /// This is currently broken and will always be 0. /// public uint CompletedParts; /// /// Since wimlib v1.13.4: Like , but counts the compressed size. /// public ulong CompletedCompressedBytes; } #endregion #region ScanProgress /// /// Dentry scan status, valid on SCAN_DENTRY. /// public enum ScanDentryStatus : uint { /// /// File looks okay and will be captured. /// Ok = 0, /// /// File is being excluded from capture due to the capture configuration. /// Excluded = 1, /// /// File is being excluded from capture due to being of an unsupported type. /// Unsupported = 2, /// /// The file is an absolute symbolic link or junction that points into the capture directory, and /// reparse-point fixups are enabled, so its target is being adjusted. /// (Reparse point fixups can be disabled with the flag .) /// FixedSymlink = 3, /// /// Reparse-point fixups are enabled, but the file is an absolute symbolic link or junction that does not /// point into the capture directory, so its target is not being adjusted. /// NotFixedSymlink = 4, } /// /// Valid on messages SCAN_BEGIN, SCAN_DENTRY, and SCAN_END. /// [StructLayout(LayoutKind.Sequential)] public class ScanProgress { /// /// Top-level directory being scanned; or, when capturing an NTFS volume with AddFlags.NTFS, /// this is instead the path to the file or block device that contains the NTFS volume being scanned. /// public string Source => Wim.Lib.PtrToStringAuto(_sourcePtr); private IntPtr _sourcePtr; /// /// Path to the file (or directory) that has been scanned, valid on SCAN_DENTRY. /// When capturing an NTFS volume with ::WIMLIB_ADD_FLAG_NTFS, this path will be relative to the root of the NTFS volume. /// public string CurPath => Wim.Lib.PtrToStringAuto(_curPathPtr); private IntPtr _curPathPtr; /// /// Dentry scan status, valid on SCAN_DENTRY. /// public ScanDentryStatus Status; /// /// - wim_target_path /// Target path in the image. Only valid on messages /// SCAN_BEGIN and /// SCAN_END. /// /// - symlink_target /// For SCAN_DENTRY and a status of WIMLIB_SCAN_DENTRY_FIXED_SYMLINK or WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK, /// this is the target of the absolute symbolic link or junction. /// public string WimTargetPathSymlinkTarget => Wim.Lib.PtrToStringAuto(_wimTargetPathSymlinkTargetPtr); private IntPtr _wimTargetPathSymlinkTargetPtr; /// /// The number of directories scanned so far, not counting excluded/unsupported files. /// public ulong NumDirsScanned; /// /// The number of non-directories scanned so far, not counting excluded/unsupported files. /// public ulong NumNonDirsScanned; /// /// The number of bytes of file data detected so far, not counting excluded/unsupported files. /// public ulong NumBytesScanned; } #endregion #region ExtractProgress /// /// Valid on messages /// EXTRACT_SPWM_PART_BEGIN, /// EXTRACT_IMAGE_BEGIN, /// EXTRACT_TREE_BEGIN, /// EXTRACT_FILE_STRUCTURE, /// EXTRACT_STREAMS, /// EXTRACT_METADATA, /// EXTRACT_TREE_END, and /// EXTRACT_IMAGE_END. /// /// Note: most of the time of an extraction operation will be spent extracting file data, and the application will receive /// EXTRACT_STREAMS during this time. Using completed_bytes and @p total_bytes, the application can calculate a /// percentage complete. However, there is no way for applications to know which file is currently being extracted. /// This is by design because the best way to complete the extraction operation is not necessarily file-by-file. /// [StructLayout(LayoutKind.Sequential)] public class ExtractProgress { /// /// The 1-based index of the image from which files are being extracted. /// public uint Image; /// /// Extraction flags being used. /// public uint ExtractFlags; /// /// If the WimStruct from which the extraction being performed has a backing file, /// then this is an absolute path to that backing file. Otherwise, this is null. /// public string WimFileName => Wim.Lib.PtrToStringAuto(_wimFileNamePtr); private IntPtr _wimFileNamePtr; /// /// Name of the image from which files are being extracted, or the empty string if the image is unnamed. /// public string ImageName => Wim.Lib.PtrToStringAuto(_imageNamePtr); private IntPtr _imageNamePtr; /// /// Path to the directory or NTFS volume to which the files are being extracted. /// public string Target => Wim.Lib.PtrToStringAuto(_targetPtr); private IntPtr _targetPtr; /// /// Reserved. /// private IntPtr _reserved; /// /// The number of bytes of file data that will be extracted. /// public ulong TotalBytes; /// /// The number of bytes of file data that have been extracted so far. /// This starts at 0 and ends at TotalBytes. /// public ulong CompletedBytes; /// /// The number of file streams that will be extracted. This will often be similar to the "number of files", /// but for several reasons (hard links, named data streams, empty files, etc.) it can be different. /// public ulong TotalStreams; /// /// The number of file streams that have been extracted so far. /// This starts at 0 and ends at @p total_streams. /// public ulong CompletedStreams; /// /// Currently only used for /// EXTRACT_SPWM_PART_BEGIN. /// public uint PartNumber; /// /// Currently only used for /// EXTRACT_SPWM_PART_BEGIN. /// public uint TotalParts; /// /// Currently only used for /// EXTRACT_SPWM_PART_BEGIN. /// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] Guid; /// /// For EXTRACT_FILE_STRUCTURE and EXTRACT_METADATA messages, /// this is the number of files that have been processed so far. /// Once the corresponding phase of extraction is complete, this value will be equal to EndFileCount. /// public ulong CurrentFileCount; /// /// For EXTRACT_FILE_STRUCTURE and EXTRACT_METADATA messages, /// this is total number of files that will be processed. /// /// This number is provided for informational purposes only, e.g. for a progress bar. /// This number will not necessarily be equal to the number of files actually being extracted. /// This is because extraction backends are free to implement an extraction algorithm that might be more efficient than /// processing every file in the "extract file structure" and "extract file metadata" phases. /// For example, the current implementation of the UNIX extraction backend will create /// files on-demand during the "extract file data" phase. /// Therefore, when using that particular extraction backend, EndFileCount will only include directories and empty files. /// public ulong EndFileCount; } #endregion #region RenameProgress /// /// Valid on messages RENAME. /// [StructLayout(LayoutKind.Sequential)] public class RenameProgress { /// /// Name of the temporary file that the WIM was written to. /// public string From => Wim.Lib.PtrToStringAuto(_fromPtr); private IntPtr _fromPtr; /// /// Name of the original WIM file to which the temporary file is /// being renamed. /// public string To => Wim.Lib.PtrToStringAuto(_toPtr); private IntPtr _toPtr; } #endregion #region UpdateProgress /// /// Valid on messages UPDATE_BEGIN_COMMAND and UPDATE_END_COMMAND. /// /// /// Wrapper of ProgressInfo_UpdateBase /// public class UpdateProgress { /// /// Name of the temporary file that the WIM was written to. /// public UpdateCommand Command; /// /// Number of update commands that have been completed so far. /// public ulong CompletedCommands => CompletedCommandsVal.ToUInt64(); internal UIntPtr CompletedCommandsVal; /// /// Number of update commands that are being executed as part of this call to Wim.UpdateImage(). /// public ulong TotalCommands => TotalCommandsVal.ToUInt64(); internal UIntPtr TotalCommandsVal; } /// /// Valid on messages UPDATE_BEGIN_COMMAND and UPDATE_END_COMMAND. /// [StructLayout(LayoutKind.Sequential)] internal class UpdateProgressBase { /// /// Name of the temporary file that the WIM was written to. /// private IntPtr _cmdPtr; private UpdateCommand32 Cmd32 => Marshal.PtrToStructure(_cmdPtr); private UpdateCommand64 Cmd64 => Marshal.PtrToStructure(_cmdPtr); public UpdateCommand Command { get { return (Wim.Lib.PlatformBitness) switch { PlatformBitness.Bit32 => Cmd32.ToManagedClass(), PlatformBitness.Bit64 => Cmd64.ToManagedClass(), _ => throw new PlatformNotSupportedException(), }; } } /// /// Number of update commands that have been completed so far. /// public UIntPtr CompletedCommandsVal; /// /// Number of update commands that are being executed as part of this call to Wim.UpdateImage(). /// public UIntPtr TotalCommandsVal; public UpdateProgress ToManaged() { return new UpdateProgress { Command = this.Command, CompletedCommandsVal = this.CompletedCommandsVal, TotalCommandsVal = this.TotalCommandsVal, }; } } #endregion #region IntegrityProgress /// /// Valid on messages VERIFY_INTEGRITY and CALC_INTEGRITY. /// [StructLayout(LayoutKind.Sequential)] public class IntegrityProgress { /// /// The number of bytes in the WIM file that are covered by integrity checks. /// public ulong TotalBytes; /// /// The number of bytes that have been checksummed so far. /// This starts at 0 and ends at TotalBytes. /// public ulong CompletedBytes; /// /// The number of individually checksummed "chunks" the integrity-checked region is divided into. /// public uint TotalChunks; /// /// The number of chunks that have been checksummed so far. /// This starts at 0 and ends at TotalChunks. /// public uint CompletedChunks; /// /// The size of each individually checksummed "chunk" in the integrity-checked region. /// public uint ChunkSize; /// /// For VERIFY_INTEGRITY messages, this is the path to the WIM file being checked. /// public string FileName => Wim.Lib.PtrToStringAuto(_fileNamePtr); private IntPtr _fileNamePtr; } #endregion #region SplitProgress /// /// Valid on messages SPLIT_BEGIN_PART and SPLIT_END_PART. /// [StructLayout(LayoutKind.Sequential)] public class SplitProgress { /// /// Total size of the original WIM's file and metadata resources (compressed). /// public ulong TotalBytes; /// /// Number of bytes of file and metadata resources that have been copied out of the original WIM so far. /// Will be 0 initially, and equal to TotalBytes at the end. /// public ulong CompletedBytes; /// /// Number of the split WIM part that is about to be started (SPLIT_BEGIN_PART) or has just been finished (SPLIT_END_PART). /// public uint CurPartNumber; /// /// Total number of split WIM parts that are being written. /// public uint TotalParts; /// /// Name of the split WIM part that is about to be started (SPLIT_BEGIN_PART) or has just been finished (SPLIT_END_PART). /// Since wimlib v1.7.0, the library user may change this when receiving SPLIT_BEGIN_PART in order to /// cause the next split WIM part to be written to a different location. /// public string PartName => Wim.Lib.PtrToStringAuto(_partNamePtr); private IntPtr _partNamePtr; } #endregion #region ReplaceProgress /// /// Valid on messages REPLACE_FILE_IN_WIM /// [StructLayout(LayoutKind.Sequential)] public class ReplaceProgress { /// /// Path to the file in the image that is being replaced. /// public string PathInWim => Wim.Lib.PtrToStringAuto(_pathInWimPtr); private IntPtr _pathInWimPtr; } #endregion #region WimBootExcludeProgress /// /// Valid on messages WIMBOOT_EXCLUDE /// [StructLayout(LayoutKind.Sequential)] public class WimBootExcludeProgress { /// /// Path to the file in the image. /// public string PathInWim => Wim.Lib.PtrToStringAuto(_pathInWimPtr); private IntPtr _pathInWimPtr; /// /// Path to which the file is being extracted . /// public string ExtractionInWim => Wim.Lib.PtrToStringAuto(_extractionInWimPtr); private IntPtr _extractionInWimPtr; } #endregion #region UnmountProgress /// /// Valid on messages UNMOUNT_BEGIN. /// [StructLayout(LayoutKind.Sequential)] public class UnmountProgress { /// /// Path to directory being unmounted. /// public string MountPoint => Wim.Lib.PtrToStringAuto(_mountPointPtr); private IntPtr _mountPointPtr; /// /// Path to WIM file being unmounted. /// public string MountedWim => Wim.Lib.PtrToStringAuto(_mountedWimPtr); private IntPtr _mountedWimPtr; /// /// 1-based index of image being unmounted. /// public uint MountedImage; /// /// Flags that were passed to Wim.MountImage() when the mountpoint was set up. /// public uint MountFlags; /// /// Flags passed to Wim.MountImage(). /// public uint UnmountFlags; } #endregion #region DoneWithFileProgress /// /// Valid on messages DONE_WITH_FILE. /// [StructLayout(LayoutKind.Sequential)] public class DoneWithFileProgress { /// /// Path to the file whose data has been written to the WIM file, /// or is currently being asynchronously compressed in memory, /// and therefore is no longer needed by wimlib. /// /// /// WARNING: The file data will not actually be accessible in the WIM file until the WIM file has been completely written. /// Ordinarily you should not treat this message as a green light to go ahead and delete the specified file, since /// that would result in data loss if the WIM file cannot be successfully created for any reason. /// /// If a file has multiple names (hard links), DONE_WITH_FILE will only be received for one name. /// Also, this message will not be received for empty files or reparse points (or symbolic links), /// unless they have nonempty named data streams. /// public string PathToFile => Wim.Lib.PtrToStringAuto(_pathToFilePtr); private IntPtr _pathToFilePtr; } #endregion #region VerifyImageProgress /// /// Valid on messages BEGIN_VERIFY_IMAGE and END_VERIFY_IMAGE. /// [StructLayout(LayoutKind.Sequential)] public class VerifyImageProgress { public string WimFile => Wim.Lib.PtrToStringAuto(_wimFilePtr); private IntPtr _wimFilePtr; public uint TotalImages; public uint CurrentImage; } #endregion #region VerifyStreamsProgress /// /// Valid on messages VERIFY_STREAMS. /// [StructLayout(LayoutKind.Sequential)] public class VerifyStreamsProgress { public string WimFile => Wim.Lib.PtrToStringAuto(_wimFilePtr); private IntPtr _wimFilePtr; public ulong TotalStreams; public ulong TotalBytes; public ulong CurrentStreams; public ulong CurrentBytes; } #endregion #region TestFileExclusionProgress /// /// Valid on messages TEST_FILE_EXCLUSION. /// [StructLayout(LayoutKind.Sequential)] public class TestFileExclusionProgress { /// /// Path to the file for which exclusion is being tested. /// /// UNIX capture mode: The path will be a standard relative or absolute UNIX filesystem path. /// NTFS-3G capture mode: The path will be given relative to the root of the NTFS volume, with a leading slash. /// Windows capture mode: The path will be a Win32 namespace path to the file. /// public string Path => Wim.Lib.PtrToStringAuto(_pathPtr); private IntPtr _pathPtr; /// /// Indicates whether the file or directory will be excluded from capture or not. /// This will be false by default. /// The progress function can set this to true if it decides that the file needs to be excluded. /// [MarshalAs(UnmanagedType.I1)] public bool WillExclude; } #endregion #region HandleErrorProgress /// /// Valid on messages HANDLE_ERROR. /// [StructLayout(LayoutKind.Sequential)] public class HandleErrorProgress { /// /// Path to the file for which the error occurred, or NULL if not relevant. /// public string Path => Wim.Lib.PtrToStringAuto(_pathPtr); private IntPtr _pathPtr; /// /// The wimlib error code associated with the error. /// public int ErrorCode; /// /// Indicates whether the error will be ignored or not. /// This will be false by default; the progress function may set it to true. /// [MarshalAs(UnmanagedType.I1)] public bool WillIgnore; } #endregion #endregion } ================================================ FILE: ManagedWimLib/WimLibException.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2017-2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using System; using System.Runtime.Serialization; using System.Text; namespace ManagedWimLib { #region WimException [Serializable] public class WimLibException : Exception { public ErrorCode ErrorCode; #region Constructor public WimLibException(ErrorCode errorCode) : base(ForgeErrorMessages(errorCode, true)) { ErrorCode = errorCode; } public WimLibException() { ErrorCode = ErrorCode.Success; } public WimLibException(string message) : base(message) { ErrorCode = ErrorCode.Success; } public WimLibException(string message, Exception innerException) : base(message, innerException) { ErrorCode = ErrorCode.Success; } #endregion internal static string ForgeErrorMessages(ErrorCode errorCode, bool full) { StringBuilder b = new StringBuilder(); if (full) b.Append($"[{errorCode}] "); b.Append(Wim.GetLastError() ?? Wim.GetErrorString(errorCode)); return b.ToString(); } internal static void CheckErrorCode(ErrorCode ret) { if (ret != ErrorCode.Success) throw new WimLibException(ret); } #region Serializable protected WimLibException(SerializationInfo info, StreamingContext ctx) { ErrorCode = (ErrorCode)info.GetValue(nameof(ErrorCode), typeof(ErrorCode)); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException(nameof(info)); info.AddValue(nameof(ErrorCode), ErrorCode); base.GetObjectData(info, context); } #endregion } #endregion } ================================================ FILE: ManagedWimLib/WimLibLoadManager.cs ================================================ /* Licensed under LGPLv3 C# Wrapper written by Hajin Jang Copyright (C) 2017-2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using Joveler.DynLoader; namespace ManagedWimLib { internal sealed class WimLibLoadManager : LoadManagerBase { protected override string ErrorMsgInitFirst => "Please call Wim.GlobalInit() first!"; protected override string ErrorMsgAlreadyLoaded => "ManagedWimLib is already initialized."; protected override WimLibLoader CreateLoader() => new WimLibLoader(); } } ================================================ FILE: ManagedWimLib/WimLibLoader.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2017-2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using Joveler.DynLoader; using System; using System.IO; using System.Linq; using System.Runtime.InteropServices; #pragma warning disable IDE0044 namespace ManagedWimLib { internal sealed class WimLibLoader : DynLoaderBase { #region Const internal const string MsgErrorFileNotSet = "ErrorFile is not set unable to read last error."; internal const string MsgPrintErrorsDisabled = "Error is not being logged, unable to read last error."; #endregion #region Constructor public WimLibLoader() : base() { } #endregion #region UTF-8 and UTF-16 internal Utf8d Utf8 = new Utf8d(); internal Utf16d Utf16 = new Utf16d(); #endregion #region Error Settings private string _errorFile = null; private ErrorPrintState _errorPrintState = ErrorPrintState.PrintOff; internal static readonly object _errorFileLock = new object(); internal string GetErrorFilePath() { lock (_errorFileLock) { return _errorFile; } } internal ErrorPrintState GetErrorPrintState() { lock (_errorFileLock) { return _errorPrintState; } } #endregion #region (override) DefaultLibFileName protected override string DefaultLibFileName { get { #if !NETFRAMEWORK if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return "libwim.so.15"; else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return "libwim.15.dylib"; #endif throw new PlatformNotSupportedException(); } } #endregion #region LoadFunctions, ResetFunctions protected override void LoadFunctions() { switch (UnicodeConvention) { case UnicodeConvention.Utf16: #region Error - SetErrorFile Utf16.SetErrorFile = GetFuncPtr(nameof(Utf16d.wimlib_set_error_file_by_name)); #endregion #region Add - AddEmptyImage, AddImage, AddImageMultiSource, AddTree Utf16.AddEmptyImage = GetFuncPtr(nameof(Utf16d.wimlib_add_empty_image)); Utf16.AddImage = GetFuncPtr(nameof(Utf16d.wimlib_add_image)); Utf16.AddImageMultiSourceL32 = GetFuncPtr("wimlib_add_image_multisource"); Utf16.AddImageMultiSourceL64 = GetFuncPtr("wimlib_add_image_multisource"); Utf16.AddTree = GetFuncPtr(nameof(Utf16d.wimlib_add_tree)); #endregion #region Delete - DeletePath Utf16.DeletePath = GetFuncPtr(nameof(Utf16d.wimlib_delete_path)); #endregion #region Export - ExportImage Utf16.ExportImage = GetFuncPtr(nameof(Utf16d.wimlib_export_image)); #endregion #region Extract - ExtractImage, ExtractPathList, ExtractPaths Utf16.ExtractImage = GetFuncPtr(nameof(Utf16d.wimlib_extract_image)); Utf16.ExtractPathList = GetFuncPtr(nameof(Utf16d.wimlib_extract_pathlist)); Utf16.ExtractPaths = GetFuncPtr(nameof(Utf16d.wimlib_extract_paths)); #endregion #region GetImageInfo - GetImageProperty Utf16.GetImageProperty = GetFuncPtr(nameof(Utf16d.wimlib_get_image_property)); #endregion #region GetWimInfo - IsImageNameInUse, ResolveImage Utf16.IsImageNameInUse = GetFuncPtr(nameof(Utf16d.wimlib_image_name_in_use)); Utf16.ResolveImage = GetFuncPtr(nameof(Utf16d.wimlib_resolve_image)); #endregion #region Iterate - IterateDirTree Utf16.IterateDirTree = GetFuncPtr(nameof(Utf16d.wimlib_iterate_dir_tree)); #endregion #region Join - Join, JoinWithProgress Utf16.Join = GetFuncPtr(nameof(Utf16d.wimlib_join)); Utf16.JoinWithProgress = GetFuncPtr(nameof(Utf16d.wimlib_join_with_progress)); #endregion #region Open - Open, OpenWithProgress Utf16.OpenWim = GetFuncPtr(nameof(Utf16d.wimlib_open_wim)); Utf16.OpenWimWithProgress = GetFuncPtr(nameof(Utf16d.wimlib_open_wim_with_progress)); #endregion #region Mount - MountImage (Linux Only) Utf16.MountImage = GetFuncPtr(nameof(Utf16d.wimlib_mount_image)); #endregion #region Reference - ReferenceTemplateImage Utf16.ReferenceResourceFiles = GetFuncPtr(nameof(Utf16d.wimlib_reference_resource_files)); #endregion #region Rename - RenamePath Utf16.RenamePath = GetFuncPtr(nameof(Utf16d.wimlib_rename_path)); #endregion #region SetImageInfo - SetImageDescription, SetImageFlags, SetImageName, SetImageProperty // ReSharper disable once CommentTypo // wimlib_set_image_descripton is misspelled from wimlib itself. Utf16.SetImageDescription = GetFuncPtr("wimlib_set_image_descripton"); Utf16.SetImageFlags = GetFuncPtr(nameof(Utf16d.wimlib_set_image_flags)); Utf16.SetImageName = GetFuncPtr(nameof(Utf16d.wimlib_set_image_name)); Utf16.SetImageProperty = GetFuncPtr(nameof(Utf16d.wimlib_set_image_property)); #endregion #region Split - Split Utf16.Split = GetFuncPtr(nameof(Utf16d.wimlib_split)); #endregion #region Unmount - UnmountImage, UnmountImageWithProgress (Linux Only) Utf16.UnmountImage = GetFuncPtr(nameof(Utf16d.wimlib_unmount_image)); Utf16.UnmountImageWithProgress = GetFuncPtr(nameof(Utf16d.wimlib_unmount_image_with_progress)); #endregion #region Write - Write Utf16.Write = GetFuncPtr(nameof(Utf16d.wimlib_write)); #endregion break; case UnicodeConvention.Utf8: #region Error - SetErrorFile Utf8.SetErrorFile = GetFuncPtr(nameof(Utf8d.wimlib_set_error_file_by_name)); #endregion #region Add - AddEmptyImage, AddImage, AddImageMultiSource, AddTree Utf8.AddEmptyImage = GetFuncPtr(nameof(Utf8d.wimlib_add_empty_image)); Utf8.AddImage = GetFuncPtr(nameof(Utf8d.wimlib_add_image)); Utf8.AddImageMultiSourceL32 = GetFuncPtr("wimlib_add_image_multisource"); Utf8.AddImageMultiSourceL64 = GetFuncPtr("wimlib_add_image_multisource"); Utf8.AddTree = GetFuncPtr(nameof(Utf8d.wimlib_add_tree)); #endregion #region Delete - DeletePath Utf8.DeletePath = GetFuncPtr(nameof(Utf8d.wimlib_delete_path)); #endregion #region Export - ExportImage Utf8.ExportImage = GetFuncPtr(nameof(Utf8d.wimlib_export_image)); #endregion #region Extract - ExtractImage, ExtractPathList, ExtractPaths Utf8.ExtractImage = GetFuncPtr(nameof(Utf8d.wimlib_extract_image)); Utf8.ExtractPathList = GetFuncPtr(nameof(Utf8d.wimlib_extract_pathlist)); Utf8.ExtractPaths = GetFuncPtr(nameof(Utf8d.wimlib_extract_paths)); #endregion #region GetImageInfo - GetImageProperty Utf8.GetImageProperty = GetFuncPtr(nameof(Utf8d.wimlib_get_image_property)); #endregion #region GetWimInfo - IsImageNameInUse, ResolveImage Utf8.IsImageNameInUse = GetFuncPtr(nameof(Utf8d.wimlib_image_name_in_use)); Utf8.ResolveImage = GetFuncPtr(nameof(Utf8d.wimlib_resolve_image)); #endregion #region Iterate - IterateDirTree Utf8.IterateDirTree = GetFuncPtr(nameof(Utf8d.wimlib_iterate_dir_tree)); #endregion #region Join - Join, JoinWithProgress Utf8.Join = GetFuncPtr(nameof(Utf8d.wimlib_join)); Utf8.JoinWithProgress = GetFuncPtr(nameof(Utf8d.wimlib_join_with_progress)); #endregion #region Open - Open, OpenWithProgress Utf8.OpenWim = GetFuncPtr(nameof(Utf8d.wimlib_open_wim)); Utf8.OpenWimWithProgress = GetFuncPtr(nameof(Utf8d.wimlib_open_wim_with_progress)); #endregion #region Mount - MountImage (Linux Only) Utf8.MountImage = GetFuncPtr(nameof(Utf8d.wimlib_mount_image)); #endregion #region Reference - ReferenceTemplateImage Utf8.ReferenceResourceFiles = GetFuncPtr(nameof(Utf8d.wimlib_reference_resource_files)); #endregion #region Rename - RenamePath Utf8.RenamePath = GetFuncPtr(nameof(Utf8d.wimlib_rename_path)); #endregion #region SetImageInfo - SetImageDescription, SetImageFlags, SetImageName, SetImageProperty // ReSharper disable once CommentTypo // wimlib_set_image_descripton is misspelled from wimlib itself. Utf8.SetImageDescription = GetFuncPtr("wimlib_set_image_descripton"); Utf8.SetImageFlags = GetFuncPtr(nameof(Utf8d.wimlib_set_image_flags)); Utf8.SetImageName = GetFuncPtr(nameof(Utf8d.wimlib_set_image_name)); Utf8.SetImageProperty = GetFuncPtr(nameof(Utf8d.wimlib_set_image_property)); #endregion #region Split - Split Utf8.Split = GetFuncPtr(nameof(Utf8d.wimlib_split)); #endregion #region Unmount - UnmountImage, UnmountImageWithProgress (Linux Only) Utf8.UnmountImage = GetFuncPtr(nameof(Utf8d.wimlib_unmount_image)); Utf8.UnmountImageWithProgress = GetFuncPtr(nameof(Utf8d.wimlib_unmount_image_with_progress)); #endregion #region Write - Write Utf8.Write = GetFuncPtr(nameof(Utf8d.wimlib_write)); #endregion break; } #region Global - GlobalInit, GlobalCleanup GlobalInit = GetFuncPtr(nameof(wimlib_global_init)); GlobalCleanup = GetFuncPtr(nameof(wimlib_global_cleanup)); #endregion #region Error - GetErrorString, SetPrintErrors GetErrorString = GetFuncPtr(nameof(wimlib_get_error_string)); SetPrintErrorsPtr = GetFuncPtr(nameof(wimlib_set_print_errors)); #endregion #region Create - CreateWim CreateNewWim = GetFuncPtr(nameof(wimlib_create_new_wim)); #endregion #region Delete - DeleteImage DeleteImage = GetFuncPtr(nameof(wimlib_delete_image)); #endregion #region Free - Free Free = GetFuncPtr(nameof(wimlib_free)); #endregion #region GetImageInfo - GetImageDescription, GetImageName GetImageDescription = GetFuncPtr(nameof(wimlib_get_image_description)); GetImageName = GetFuncPtr(nameof(wimlib_get_image_name)); #endregion #region GetWimInfo - GetWimInfo, GetXmlData GetWimInfo = GetFuncPtr(nameof(wimlib_get_wim_info)); GetXmlData = GetFuncPtr(nameof(wimlib_get_xml_data)); #endregion #region GetVersion - GetVersion, GetVersionString GetVersion = GetFuncPtr(nameof(wimlib_get_version)); GetVersionString = GetFuncPtr(nameof(wimlib_get_version_string)); #endregion #region Iterate - IterateLookupTable IterateLookupTable = GetFuncPtr(nameof(wimlib_iterate_lookup_table)); #endregion #region Reference - ReferenceTemplateImage, ReferenceResourceFiles, ReferenceResources ReferenceResources = GetFuncPtr(nameof(wimlib_reference_resources)); ReferenceTemplateImage = GetFuncPtr(nameof(wimlib_reference_template_image)); #endregion #region Callback - RegsiterProgressFunction RegisterProgressFunction = GetFuncPtr(nameof(wimlib_register_progress_function)); #endregion #region SetImageInfo - SetWimInfo SetWimInfo = GetFuncPtr(nameof(wimlib_set_wim_info)); #endregion #region SetOutput - SetOutputChunkSize, SetOutputPackChunkSize, SetOutputCompressionType, SetOutputPackCompressionType SetOutputChunkSize = GetFuncPtr(nameof(wimlib_set_output_chunk_size)); SetOutputPackChunkSize = GetFuncPtr(nameof(wimlib_set_output_pack_chunk_size)); SetOutputCompressionType = GetFuncPtr(nameof(wimlib_set_output_compression_type)); SetOutputPackCompressionType = GetFuncPtr(nameof(wimlib_set_output_pack_compression_type)); #endregion #region Verify - VerifyWim VerifyWim = GetFuncPtr(nameof(wimlib_verify_wim)); #endregion #region Update - UpdateImage UpdateImage32 = GetFuncPtr("wimlib_update_image"); UpdateImage64 = GetFuncPtr("wimlib_update_image"); #endregion #region Write - Overwrite Overwrite = GetFuncPtr(nameof(wimlib_overwrite)); #endregion #region CompressInfo - SetDefaultCompressionLevel, GetCompressorNeededMemory SetDefaultCompressionLevel = GetFuncPtr(nameof(wimlib_set_default_compression_level)); GetCompressorNeededMemory = GetFuncPtr(nameof(wimlib_get_compressor_needed_memory)); #endregion #region Compressor - CreateCompressor, FreeCompressor, Compress CreateCompressor = GetFuncPtr(nameof(wimlib_create_compressor)); FreeCompressor = GetFuncPtr(nameof(wimlib_free_compressor)); Compress = GetFuncPtr(nameof(wimlib_compress)); #endregion #region Decompressor - CreateDecompressor, FreeDecompressor, Decompress CreateDecompressor = GetFuncPtr(nameof(wimlib_create_decompressor)); FreeDecompressor = GetFuncPtr(nameof(wimlib_free_decompressor)); Decompress = GetFuncPtr(nameof(wimlib_decompress)); #endregion #region (Code) Set ErrorFile and PrintError SetErrorFile(); #endregion } protected override void ResetFunctions() { #region (Code) Cleanup ErrorFile SetPrintErrors(false); #endregion #region Global - GlobalInit, GlobalCleanup GlobalInit = null; GlobalCleanup = null; #endregion #region Error - GetErrorString, SetErrorFile, SetPrintErrors GetErrorString = null; Utf16.SetErrorFile = null; Utf8.SetErrorFile = null; SetPrintErrorsPtr = null; #endregion #region Add - AddEmptyImage, AddImage, AddImageMultiSource, AddTree Utf16.AddEmptyImage = null; Utf16.AddImage = null; Utf16.AddImageMultiSourceL32 = null; Utf16.AddImageMultiSourceL64 = null; Utf16.AddTree = null; Utf8.AddEmptyImage = null; Utf8.AddImage = null; Utf8.AddImageMultiSourceL32 = null; Utf8.AddImageMultiSourceL64 = null; Utf8.AddTree = null; #endregion #region Create - CreateWim CreateNewWim = null; #endregion #region Delete - DeleteImage, DeletePath DeleteImage = null; Utf16.DeletePath = null; Utf8.DeletePath = null; #endregion #region Export - ExportImage Utf16.ExportImage = null; Utf8.ExportImage = null; #endregion #region Extract - ExtractImage, ExtractPathList, ExtractPaths Utf16.ExtractImage = null; Utf16.ExtractPathList = null; Utf16.ExtractPaths = null; Utf8.ExtractImage = null; Utf8.ExtractPathList = null; Utf8.ExtractPaths = null; #endregion #region Free - Free Free = null; #endregion #region GetImageInfo - GetImageDescription, GetImageName, GetImageProperty GetImageDescription = null; GetImageName = null; Utf16.GetImageProperty = null; Utf8.GetImageProperty = null; #endregion #region GetWimInfo - GetWimInfo, GetXmlData, IsImageNameInUse, ResolveImage GetWimInfo = null; GetXmlData = null; Utf16.IsImageNameInUse = null; Utf16.ResolveImage = null; Utf8.IsImageNameInUse = null; Utf8.ResolveImage = null; #endregion #region GetVersion - GetVersion, GetVersionString GetVersion = null; GetVersionString = null; #endregion #region Iterate - IterateDirTree, IterateLookupTable Utf16.IterateDirTree = null; Utf8.IterateDirTree = null; IterateLookupTable = null; #endregion #region Join - Join, JoinWithProgress Utf16.Join = null; Utf16.JoinWithProgress = null; Utf8.Join = null; Utf8.JoinWithProgress = null; #endregion #region Open - Open, OpenWithProgress Utf16.OpenWim = null; Utf16.OpenWimWithProgress = null; Utf8.OpenWim = null; Utf8.OpenWimWithProgress = null; #endregion #region Mount - MountImage (Linux Only) Utf16.MountImage = null; Utf8.MountImage = null; #endregion #region Reference - ReferenceTemplateImage, ReferenceResourceFiles, ReferenceResources Utf16.ReferenceResourceFiles = null; Utf8.ReferenceResourceFiles = null; ReferenceResources = null; ReferenceTemplateImage = null; #endregion #region Callback - RegsiterProgressFunction RegisterProgressFunction = null; #endregion #region Rename - RenamePath Utf16.RenamePath = null; Utf8.RenamePath = null; #endregion #region SetImageInfo - SetImageDescription, SetImageFlags, SetImageName, SetImageProperty, SetWimInfo Utf16.SetImageDescription = null; Utf16.SetImageFlags = null; Utf16.SetImageName = null; Utf16.SetImageProperty = null; Utf8.SetImageDescription = null; Utf8.SetImageFlags = null; Utf8.SetImageName = null; Utf8.SetImageProperty = null; SetWimInfo = null; #endregion #region SetOutput - SetOutputChunkSize, SetOutputPackChunkSize, SetOutputCompressionType, SetOutputPackCompressionType SetOutputChunkSize = null; SetOutputPackChunkSize = null; SetOutputCompressionType = null; SetOutputPackCompressionType = null; #endregion #region Split - Split Utf16.Split = null; Utf8.Split = null; #endregion #region Verify - VerifyWim VerifyWim = null; #endregion #region Unmount - UnmountImage, UnmountImageWithProgress (Linux Only) Utf16.UnmountImage = null; Utf16.UnmountImageWithProgress = null; Utf8.UnmountImage = null; Utf8.UnmountImageWithProgress = null; #endregion #region Update - UpdateImage UpdateImage32 = null; UpdateImage64 = null; #endregion #region Write - Write, Overwrite Utf16.Write = null; Utf8.Write = null; Overwrite = null; #endregion #region CompressInfo - SetDefaultCompressionLevel, GetCompressorNeededMemory SetDefaultCompressionLevel = null; GetCompressorNeededMemory = null; #endregion #region Compressor - CreateCompressor, FreeCompressor, Compress CreateCompressor = null; FreeCompressor = null; Compress = null; #endregion #region Decompressor - CreateDecompressor, FreeDecompressor, Decompress CreateDecompressor = null; FreeDecompressor = null; Decompress = null; #endregion } #endregion #region WimLib Function Pointers #region UTF-16 Instances internal class Utf16d { internal const UnmanagedType StrType = UnmanagedType.LPWStr; internal const CharSet StructCharSet = CharSet.Unicode; #region Error - SetErrorFile /// /// Set the path to the file to which the library will print error and warning messages. /// The library will open this file for appending. /// /// This also enables error messages, as if by a call to . /// /// /// 0 on success; a ::wimlib_error_code value on failure /// WIMLIB_ERR_OPEN: The file named by @p path could not be opened for appending /// [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_error_file_by_name( [MarshalAs(StrType)] string path); internal wimlib_set_error_file_by_name SetErrorFile; #endregion #region Add - AddEmptyImage, AddImage, AddImageMultiSource, AddTree [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_empty_image( IntPtr wim, [MarshalAs(StrType)] string name, out int new_idx_ret); internal wimlib_add_empty_image AddEmptyImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_image( IntPtr wim, [MarshalAs(StrType)] string source, [MarshalAs(StrType)] string name, [MarshalAs(StrType)] string config_file, AddFlags add_flags); internal wimlib_add_image AddImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_image_multisource_l32( IntPtr wim, [MarshalAs(UnmanagedType.LPArray)] CaptureSourceBaseL32[] sources, UIntPtr num_sources, // size_t [MarshalAs(StrType)] string name, [MarshalAs(StrType)] string config_file, AddFlags add_flags); internal wimlib_add_image_multisource_l32 AddImageMultiSourceL32; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_image_multisource_l64( IntPtr wim, [MarshalAs(UnmanagedType.LPArray)] CaptureSourceBaseL64[] sources, UIntPtr num_sources, // size_t [MarshalAs(StrType)] string name, [MarshalAs(StrType)] string config_file, AddFlags add_flags); internal wimlib_add_image_multisource_l64 AddImageMultiSourceL64; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_tree( IntPtr wim, int image, [MarshalAs(StrType)] string fs_source_path, [MarshalAs(StrType)] string wim_target_path, AddFlags add_flags); internal wimlib_add_tree AddTree; #endregion #region Delete - DeletePath [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_delete_path( IntPtr wim, int image, [MarshalAs(StrType)] string path, DeleteFlags delete_flags); internal wimlib_delete_path DeletePath; #endregion #region Export - ExportImage [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_export_image( IntPtr src_wim, int src_image, IntPtr dest_wim, [MarshalAs(StrType)] string dest_name, [MarshalAs(StrType)] string dest_description, ExportFlags export_flags); internal wimlib_export_image ExportImage; #endregion #region Extract - ExtractImage, ExtractPaths, ExtractPathList [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_extract_image( IntPtr wim, int image, [MarshalAs(StrType)] string target, ExtractFlags extract_flags); internal wimlib_extract_image ExtractImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_extract_pathlist( IntPtr wim, int image, [MarshalAs(StrType)] string target, [MarshalAs(StrType)] string path_list_file, ExtractFlags extract_flags); internal wimlib_extract_pathlist ExtractPathList; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_extract_paths( IntPtr wim, int image, [MarshalAs(StrType)] string target, [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] paths, UIntPtr num_paths, // size_t ExtractFlags extract_flags); internal wimlib_extract_paths ExtractPaths; #endregion #region GetImageInfo - GetImageProperty [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr wimlib_get_image_property( IntPtr wim, int image, [MarshalAs(StrType)] string property_name); internal wimlib_get_image_property GetImageProperty; #endregion #region GetWimInfo - IsImageNameInUse, ResolveImage [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] internal delegate bool wimlib_image_name_in_use( IntPtr wim, [MarshalAs(StrType)] string name); internal wimlib_image_name_in_use IsImageNameInUse; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int wimlib_resolve_image( IntPtr wim, [MarshalAs(StrType)] string image_name_or_num); internal wimlib_resolve_image ResolveImage; #endregion #region Iterate - IterateDirTree [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] internal delegate int wimlib_iterate_dir_tree( IntPtr wim, int image, [MarshalAs(StrType)] string path, IterateDirTreeFlags flags, [MarshalAs(UnmanagedType.FunctionPtr)] NativeIterateDirTreeCallback cb, IntPtr user_ctx); internal wimlib_iterate_dir_tree IterateDirTree; #endregion #region Join - Join, JoinWithProgress [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_join( [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] swms, uint num_swms, [MarshalAs(StrType)] string output_path, OpenFlags swms_open_flags, WriteFlags write_flags); internal wimlib_join Join; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_join_with_progress( [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] swms, uint num_swms, [MarshalAs(StrType)] string output_path, OpenFlags swms_open_flags, WriteFlags write_flags, [MarshalAs(UnmanagedType.FunctionPtr)] NativeProgressFunc progfunc, IntPtr progctx); internal wimlib_join_with_progress JoinWithProgress; #endregion #region Open - OpenWim, OpenWithProgress [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_open_wim( [MarshalAs(StrType)] string wim_file, OpenFlags open_flags, out IntPtr wim_ret); internal wimlib_open_wim OpenWim; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_open_wim_with_progress( [MarshalAs(StrType)] string wim_file, OpenFlags open_flags, out IntPtr wim_ret, [MarshalAs(UnmanagedType.FunctionPtr)] NativeProgressFunc progfunc, IntPtr progctx); internal wimlib_open_wim_with_progress OpenWimWithProgress; #endregion #region Mount - MountImage (Linux Only) [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_mount_image( IntPtr wim, int image, [MarshalAs(StrType)] string dir, MountFlags mount_flags, [MarshalAs(StrType)] string staging_dir); internal wimlib_mount_image MountImage; #endregion #region Reference - ReferenceResourceFiles [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_reference_resource_files( IntPtr wim, [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] resource_wimfiles_or_globs, uint count, RefFlags ref_flags, OpenFlags open_flags); internal wimlib_reference_resource_files ReferenceResourceFiles; #endregion #region Rename - RenamePath [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_rename_path( IntPtr wim, int image, [MarshalAs(StrType)] string source_path, [MarshalAs(StrType)] string dest_path); internal wimlib_rename_path RenamePath; #endregion #region SetImageInfo - SetImageDescription, SetImageFlags, SetImageName, SetImageProperty [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_description( IntPtr wim, int image, [MarshalAs(StrType)] string description); internal wimlib_set_image_description SetImageDescription; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_flags( IntPtr wim, int image, [MarshalAs(StrType)] string flags); internal wimlib_set_image_flags SetImageFlags; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_name( IntPtr wim, int image, [MarshalAs(StrType)] string name); internal wimlib_set_image_name SetImageName; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_property( IntPtr wim, int image, [MarshalAs(StrType)] string property_name, [MarshalAs(StrType)] string property_value); internal wimlib_set_image_property SetImageProperty; #endregion #region Split - Split [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_split( IntPtr wim, [MarshalAs(StrType)] string swm_name, ulong part_size, WriteFlags write_flags); internal wimlib_split Split; #endregion #region Unmount - UnmountImage, UnmountImageWithProgress (Linux Only) [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_unmount_image( [MarshalAs(StrType)] string dir, UnmountFlags unmount_flags); internal wimlib_unmount_image UnmountImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_unmount_image_with_progress( [MarshalAs(StrType)] string dir, UnmountFlags unmount_flags, [MarshalAs(UnmanagedType.FunctionPtr)] NativeProgressFunc progfunc, IntPtr progctx); internal wimlib_unmount_image_with_progress UnmountImageWithProgress; #endregion #region Write - Write [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_write( IntPtr wim, [MarshalAs(StrType)] string path, int image, WriteFlags write_flags, uint num_threads); internal wimlib_write Write; #endregion #region struct CaptureSourceBase /// /// An array of these structures is passed to /// to specify the sources from which to create a WIM image. /// /// /// For LLP64 platforms (Windows) /// [StructLayout(LayoutKind.Sequential, CharSet = StructCharSet)] internal struct CaptureSourceBaseL32 { /// /// Absolute or relative path to a file or directory on the external filesystem to be included in the image. /// public string FsSourcePath; /// /// Destination path in the image. /// To specify the root directory of the image, use . /// public string WimTargetPath; /// /// Reserved; set to 0. /// private int _reserved; public CaptureSourceBaseL32(string fsSourcePath, string wimTargetPath) { FsSourcePath = fsSourcePath; WimTargetPath = wimTargetPath; _reserved = 0; } }; /// /// An array of these structures is passed to /// to specify the sources from which to create a WIM image. /// /// /// For LP64 platforms (64bit POSIX) /// [StructLayout(LayoutKind.Sequential, CharSet = StructCharSet)] internal struct CaptureSourceBaseL64 { /// /// Absolute or relative path to a file or directory on the external filesystem to be included in the image. /// public string FsSourcePath; /// /// Destination path in the image. /// To specify the root directory of the image, use . /// public string WimTargetPath; /// /// Reserved; set to 0. /// private long _reserved; public CaptureSourceBaseL64(string fsSourcePath, string wimTargetPath) { FsSourcePath = fsSourcePath; WimTargetPath = wimTargetPath; _reserved = 0; } }; #endregion } #endregion #region UTF-8 Instances internal class Utf8d { internal const UnmanagedType StrType = UnmanagedType.LPStr; internal const CharSet StructCharSet = CharSet.Ansi; #region Error - SetErrorFile /// /// Set the path to the file to which the library will print error and warning messages. /// The library will open this file for appending. /// /// This also enables error messages, as if by a call to wimlib_set_print_errors(true). /// /// /// WIMLIB_ERR_OPEN: The file named by @p path could not be opened for appending. /// WIMLIB_ERR_UNSUPPORTED: wimlib was compiled using the --without-error-messages option. /// /// 0 on success; a ::wimlib_error_code value on failure. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_error_file_by_name( [MarshalAs(StrType)] string path); internal wimlib_set_error_file_by_name SetErrorFile; #endregion #region Add - AddEmptyImage, AddImage, AddImageMultiSource, AddTree [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_empty_image( IntPtr wim, [MarshalAs(StrType)] string name, out int new_idx_ret); internal wimlib_add_empty_image AddEmptyImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_image( IntPtr wim, [MarshalAs(StrType)] string source, [MarshalAs(StrType)] string name, [MarshalAs(StrType)] string config_file, AddFlags add_flags); internal wimlib_add_image AddImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_image_multisource_l32( IntPtr wim, [MarshalAs(UnmanagedType.LPArray)] CaptureSourceBaseL32[] sources, UIntPtr num_sources, // size_t [MarshalAs(StrType)] string name, [MarshalAs(StrType)] string config_file, AddFlags add_flags); internal wimlib_add_image_multisource_l32 AddImageMultiSourceL32; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_image_multisource_l64( IntPtr wim, [MarshalAs(UnmanagedType.LPArray)] CaptureSourceBaseL64[] sources, UIntPtr num_sources, // size_t [MarshalAs(StrType)] string name, [MarshalAs(StrType)] string config_file, AddFlags add_flags); internal wimlib_add_image_multisource_l64 AddImageMultiSourceL64; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_add_tree( IntPtr wim, int image, [MarshalAs(StrType)] string fs_source_path, [MarshalAs(StrType)] string wim_target_path, AddFlags add_flags); internal wimlib_add_tree AddTree; #endregion #region Delete - DeletePath [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_delete_path( IntPtr wim, int image, [MarshalAs(StrType)] string path, DeleteFlags delete_flags); internal wimlib_delete_path DeletePath; #endregion #region Export - ExportImage [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_export_image( IntPtr src_wim, int src_image, IntPtr dest_wim, [MarshalAs(StrType)] string dest_name, [MarshalAs(StrType)] string dest_description, ExportFlags export_flags); internal wimlib_export_image ExportImage; #endregion #region Extract - ExtractImage, ExtractPaths, ExtractPathList [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_extract_image( IntPtr wim, int image, [MarshalAs(StrType)] string target, ExtractFlags extract_flags); internal wimlib_extract_image ExtractImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_extract_pathlist( IntPtr wim, int image, [MarshalAs(StrType)] string target, [MarshalAs(StrType)] string path_list_file, ExtractFlags extract_flags); internal wimlib_extract_pathlist ExtractPathList; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_extract_paths( IntPtr wim, int image, [MarshalAs(StrType)] string target, [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] paths, UIntPtr num_paths, // size_t ExtractFlags extract_flags); internal wimlib_extract_paths ExtractPaths; #endregion #region GetImageInfo - GetImageProperty [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr wimlib_get_image_property( IntPtr wim, int image, [MarshalAs(StrType)] string property_name); internal wimlib_get_image_property GetImageProperty; #endregion #region GetWimInfo - IsImageNameInUse, ResolveImage [UnmanagedFunctionPointer(CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.I1)] internal delegate bool wimlib_image_name_in_use( IntPtr wim, [MarshalAs(StrType)] string name); internal wimlib_image_name_in_use IsImageNameInUse; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int wimlib_resolve_image( IntPtr wim, [MarshalAs(StrType)] string image_name_or_num); internal wimlib_resolve_image ResolveImage; #endregion #region Iterate - IterateDirTree [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] internal delegate int wimlib_iterate_dir_tree( IntPtr wim, int image, [MarshalAs(StrType)] string path, IterateDirTreeFlags flags, [MarshalAs(UnmanagedType.FunctionPtr)] NativeIterateDirTreeCallback cb, IntPtr user_ctx); internal wimlib_iterate_dir_tree IterateDirTree; #endregion #region Join - Join, JoinWithProgress [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_join( [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] swms, uint num_swms, [MarshalAs(StrType)] string output_path, OpenFlags swms_open_flags, WriteFlags write_flags); internal wimlib_join Join; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_join_with_progress( [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] swms, uint num_swms, [MarshalAs(StrType)] string output_path, OpenFlags swms_open_flags, WriteFlags write_flags, [MarshalAs(UnmanagedType.FunctionPtr)] NativeProgressFunc progfunc, IntPtr progctx); internal wimlib_join_with_progress JoinWithProgress; #endregion #region Open - OpenWim, OpenWithProgress [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_open_wim( [MarshalAs(StrType)] string wim_file, OpenFlags open_flags, out IntPtr wim_ret); internal wimlib_open_wim OpenWim; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_open_wim_with_progress( [MarshalAs(StrType)] string wim_file, OpenFlags open_flags, out IntPtr wim_ret, [MarshalAs(UnmanagedType.FunctionPtr)] NativeProgressFunc progfunc, IntPtr progctx); internal wimlib_open_wim_with_progress OpenWimWithProgress; #endregion #region Mount - MountImage (Linux Only) [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_mount_image( IntPtr wim, int image, [MarshalAs(StrType)] string dir, MountFlags mount_flags, [MarshalAs(StrType)] string staging_dir); internal wimlib_mount_image MountImage; #endregion #region Reference - ReferenceResourceFiles [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_reference_resource_files( IntPtr wim, [MarshalAs(UnmanagedType.LPArray, ArraySubType = StrType)] string[] resource_wimfiles_or_globs, uint count, RefFlags ref_flags, OpenFlags open_flags); internal wimlib_reference_resource_files ReferenceResourceFiles; #endregion #region Rename - RenamePath [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_rename_path( IntPtr wim, int image, [MarshalAs(StrType)] string source_path, [MarshalAs(StrType)] string dest_path); internal wimlib_rename_path RenamePath; #endregion #region SetImageInfo - SetImageDescription, SetImageFlags, SetImageName, SetImageProperty [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_description( IntPtr wim, int image, [MarshalAs(StrType)] string description); internal wimlib_set_image_description SetImageDescription; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_flags( IntPtr wim, int image, [MarshalAs(StrType)] string flags); internal wimlib_set_image_flags SetImageFlags; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_name( IntPtr wim, int image, [MarshalAs(StrType)] string name); internal wimlib_set_image_name SetImageName; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_image_property( IntPtr wim, int image, [MarshalAs(StrType)] string property_name, [MarshalAs(StrType)] string property_value); internal wimlib_set_image_property SetImageProperty; #endregion #region Split - Split [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_split( IntPtr wim, [MarshalAs(StrType)] string swm_name, ulong part_size, WriteFlags write_flags); internal wimlib_split Split; #endregion #region Unmount - UnmountImage, UnmountImageWithProgress (Linux Only) [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_unmount_image( [MarshalAs(StrType)] string dir, UnmountFlags unmount_flags); internal wimlib_unmount_image UnmountImage; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_unmount_image_with_progress( [MarshalAs(StrType)] string dir, UnmountFlags unmount_flags, [MarshalAs(UnmanagedType.FunctionPtr)] NativeProgressFunc progfunc, IntPtr progctx); internal wimlib_unmount_image_with_progress UnmountImageWithProgress; #endregion #region Write - Write [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_write( IntPtr wim, [MarshalAs(StrType)] string path, int image, WriteFlags write_flags, uint num_threads); internal wimlib_write Write; #endregion #region struct CaptureSourceBase /// /// An array of these structures is passed to /// to specify the sources from which to create a WIM image. /// /// /// For LLP64 platforms (Windows) /// [StructLayout(LayoutKind.Sequential, CharSet = StructCharSet)] internal struct CaptureSourceBaseL32 { /// /// Absolute or relative path to a file or directory on the external filesystem to be included in the image. /// public string FsSourcePath; /// /// Destination path in the image. /// To specify the root directory of the image, use . /// public string WimTargetPath; /// /// Reserved; set to 0. /// private int _reserved; public CaptureSourceBaseL32(string fsSourcePath, string wimTargetPath) { FsSourcePath = fsSourcePath; WimTargetPath = wimTargetPath; _reserved = 0; } }; /// /// An array of these structures is passed to /// to specify the sources from which to create a WIM image. /// /// /// For LP64 platforms (64bit POSIX) /// [StructLayout(LayoutKind.Sequential, CharSet = StructCharSet)] internal struct CaptureSourceBaseL64 { /// /// Absolute or relative path to a file or directory on the external filesystem to be included in the image. /// public string FsSourcePath; /// /// Destination path in the image. /// To specify the root directory of the image, use . /// public string WimTargetPath; /// /// Reserved; set to 0. /// private long _reserved; public CaptureSourceBaseL64(string fsSourcePath, string wimTargetPath) { FsSourcePath = fsSourcePath; WimTargetPath = wimTargetPath; _reserved = 0; } }; #endregion } #endregion #region GlobalInit, GlobalCleanup [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_global_init(InitFlags initFlags); /// /// Initialization function for wimlib. /// Call before using any other wimlib function (except possibly ). /// If not done manually, this function will be called automatically with a flags argument of 0. /// This function does nothing if called again after it has already successfully run. /// internal wimlib_global_init GlobalInit; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void wimlib_global_cleanup(); /// /// Cleanup function for wimlib. /// You are not required to call this function, but it will release any global resources allocated by the library. /// internal wimlib_global_cleanup GlobalCleanup; #endregion #region Create - Callback [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate CallbackStatus NativeProgressFunc( ProgressMsg msgType, IntPtr info, IntPtr progctx); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void wimlib_register_progress_function( IntPtr wim, [MarshalAs(UnmanagedType.FunctionPtr)] NativeProgressFunc progfunc, IntPtr progctx); internal wimlib_register_progress_function RegisterProgressFunction; #endregion #region Error - GetErrorString, SetErrorFile, SetPrintErrors and Helpers [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr wimlib_get_error_string(ErrorCode code); internal wimlib_get_error_string GetErrorString; internal void SetErrorFile() { string errorFile = Path.GetTempFileName(); SetErrorFile(errorFile); } internal void SetErrorFile(string path) { if (path == null) throw new ArgumentNullException(nameof(path)); var ret = UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.SetErrorFile(path), _ => Utf8.SetErrorFile(path), }; // When ret is ErrorCode.NotSupported, wimlib was compiled using the --without-error-messages option. // In that case, ManagedWimLib must not throw WimException. if (ret == ErrorCode.Unsupported) { _errorPrintState = ErrorPrintState.NotSupported; // ErrorFile is no longer used, delete it if (_errorFile != null) { if (File.Exists(_errorFile)) File.Delete(_errorFile); _errorFile = null; } } else { WimLibException.CheckErrorCode(ret); // Set new ErrorFile and report state as ErrorPrintState.PrintOn. _errorPrintState = ErrorPrintState.PrintOn; _errorFile = path; } } /// /// Set whether wimlib can print error and warning messages to the error file, which defaults to standard error. /// Error and warning messages may provide information that cannot be determined only from returned error codes. /// /// By default, error messages are not printed. /// This setting applies globally (it is not per-WIM). /// This can be called before . /// /// 0 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_print_errors([MarshalAs(UnmanagedType.I1)] bool showMessages); private wimlib_set_print_errors SetPrintErrorsPtr; internal void SetPrintErrors(bool showMessages) { lock (_errorFileLock) { ErrorCode ret = SetPrintErrorsPtr(showMessages); // [*] v1.13.5 or earlier // When ret is ErrorCode.Unsupported, wimlib was compiled using the --without-error-messages option. // In that case, ManagedWimLib must not throw WimException. if (ret == ErrorCode.Unsupported) { _errorPrintState = ErrorPrintState.NotSupported; } else { WimLibException.CheckErrorCode(ret); _errorPrintState = showMessages ? ErrorPrintState.PrintOn : ErrorPrintState.PrintOff; } } } internal string[] GetErrors() { lock (_errorFileLock) { if (_errorFile == null) return null; if (_errorPrintState != ErrorPrintState.PrintOn) return null; using (FileStream fs = new FileStream(_errorFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (StreamReader r = new StreamReader(fs, UnicodeEncoding, false)) { return r.ReadToEnd().Split('\n').Select(x => x.Trim()).Where(x => 0 < x.Length).ToArray(); } } } internal string GetLastError() { lock (_errorFileLock) { if (_errorFile == null) return null; if (_errorPrintState != ErrorPrintState.PrintOn) return null; using (FileStream fs = new FileStream(_errorFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (StreamReader r = new StreamReader(fs, UnicodeEncoding, false)) { var lines = r.ReadToEnd().Split('\n').Select(x => x.Trim()).Where(x => 0 < x.Length); return lines.LastOrDefault(); } } } internal void ResetErrorFile() { lock (_errorFileLock) { if (_errorFile == null) return; if (_errorPrintState != ErrorPrintState.PrintOn) return; // Overwrite to Empty File using (FileStream fs = new FileStream(_errorFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) using (StreamWriter w = new StreamWriter(fs, UnicodeEncoding)) { w.WriteLine(); } } } #endregion #region Add - AddEmptyImage, AddImage, AddImageMultiSource, AddTree internal ErrorCode AddEmptyImage(IntPtr wim, string name, out int newIdxRet) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.AddEmptyImage(wim, name, out newIdxRet), _ => Utf8.AddEmptyImage(wim, name, out newIdxRet), }; } internal ErrorCode AddImage(IntPtr wim, string source, string name, string configFile, AddFlags addFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.AddImage(wim, source, name, configFile, addFlags), _ => Utf8.AddImage(wim, source, name, configFile, addFlags), }; } internal ErrorCode AddImageMultiSource(IntPtr wim, CaptureSource[] sources, UIntPtr numSources, string name, string configFile, AddFlags addFlags) { switch (UnicodeConvention) { case UnicodeConvention.Utf16: switch (PlatformLongSize) { case PlatformLongSize.Long32: Utf16d.CaptureSourceBaseL32[] capSrcsL32 = new Utf16d.CaptureSourceBaseL32[sources.Length]; for (int i = 0; i < sources.Length; i++) { CaptureSource src = sources[i]; capSrcsL32[i] = new Utf16d.CaptureSourceBaseL32 { FsSourcePath = src.FsSourcePath, WimTargetPath = src.WimTargetPath, }; } return Utf16.AddImageMultiSourceL32(wim, capSrcsL32, numSources, name, configFile, addFlags); case PlatformLongSize.Long64: Utf16d.CaptureSourceBaseL64[] capSrcsL64 = new Utf16d.CaptureSourceBaseL64[sources.Length]; for (int i = 0; i < sources.Length; i++) { CaptureSource src = sources[i]; capSrcsL64[i] = new Utf16d.CaptureSourceBaseL64 { FsSourcePath = src.FsSourcePath, WimTargetPath = src.WimTargetPath, }; } return Utf16.AddImageMultiSourceL64(wim, capSrcsL64, numSources, name, configFile, addFlags); } throw new PlatformNotSupportedException(); case UnicodeConvention.Utf8: default: switch (PlatformLongSize) { case PlatformLongSize.Long32: Utf8d.CaptureSourceBaseL32[] capSrcsL32 = new Utf8d.CaptureSourceBaseL32[sources.Length]; for (int i = 0; i < sources.Length; i++) { CaptureSource src = sources[i]; capSrcsL32[i] = new Utf8d.CaptureSourceBaseL32 { FsSourcePath = src.FsSourcePath, WimTargetPath = src.WimTargetPath, }; } return Utf8.AddImageMultiSourceL32(wim, capSrcsL32, numSources, name, configFile, addFlags); case PlatformLongSize.Long64: Utf8d.CaptureSourceBaseL64[] capSrcsL64 = new Utf8d.CaptureSourceBaseL64[sources.Length]; for (int i = 0; i < sources.Length; i++) { CaptureSource src = sources[i]; capSrcsL64[i] = new Utf8d.CaptureSourceBaseL64 { FsSourcePath = src.FsSourcePath, WimTargetPath = src.WimTargetPath, }; } return Utf8.AddImageMultiSourceL64(wim, capSrcsL64, numSources, name, configFile, addFlags); } throw new PlatformNotSupportedException(); } } internal ErrorCode AddTree(IntPtr wim, int image, string fsSourcePath, string wimTargetPath, AddFlags addFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.AddTree(wim, image, fsSourcePath, wimTargetPath, addFlags), _ => Utf8.AddTree(wim, image, fsSourcePath, wimTargetPath, addFlags), }; } #endregion #region Create - CreateWim [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_create_new_wim( CompressionType ctype, out IntPtr wim_ret); internal wimlib_create_new_wim CreateNewWim; #endregion #region Delete - DeleteImage, DeletePath [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_delete_image( IntPtr wim, int image); internal wimlib_delete_image DeleteImage; internal ErrorCode DeletePath(IntPtr wim, int image, string path, DeleteFlags deleteFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.DeletePath(wim, image, path, deleteFlags), _ => Utf8.DeletePath(wim, image, path, deleteFlags), }; } #endregion #region Export - ExportImage internal ErrorCode ExportImage(IntPtr srcWim, int srcImage, IntPtr destWim, string destName, string destDesc, ExportFlags exportFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.ExportImage(srcWim, srcImage, destWim, destName, destDesc, exportFlags), _ => Utf8.ExportImage(srcWim, srcImage, destWim, destName, destDesc, exportFlags), }; } #endregion #region Extract - ExtractImage, ExtractPaths, ExtractPathList internal ErrorCode ExtractImage(IntPtr wim, int image, string target, ExtractFlags extractFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.ExtractImage(wim, image, target, extractFlags), _ => Utf8.ExtractImage(wim, image, target, extractFlags), }; } internal ErrorCode ExtractPathList(IntPtr wim, int image, string target, string pathListFile, ExtractFlags extractFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.ExtractPathList(wim, image, target, pathListFile, extractFlags), _ => Utf8.ExtractPathList(wim, image, target, pathListFile, extractFlags), }; } internal ErrorCode ExtractPaths(IntPtr wim, int image, string target, string[] paths, UIntPtr numPaths, ExtractFlags extract_flags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.ExtractPaths(wim, image, target, paths, numPaths, extract_flags), _ => Utf8.ExtractPaths(wim, image, target, paths, numPaths, extract_flags), }; } #endregion #region Free - Free [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void wimlib_free(IntPtr wim); internal wimlib_free Free; #endregion #region GetImageInfo - GetImageDescription, GetImageName, GetImageProperty [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr wimlib_get_image_description( IntPtr wim, int image); internal wimlib_get_image_description GetImageDescription; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr wimlib_get_image_name( IntPtr wim, int image); internal wimlib_get_image_name GetImageName; internal IntPtr GetImageProperty(IntPtr wim, int image, string property_name) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.GetImageProperty(wim, image, property_name), _ => Utf8.GetImageProperty(wim, image, property_name), }; } #endregion #region GetWimInfo - GetWimInfo, GetXmlData, IsImageNameInUse, ResolveImage [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_get_wim_info( IntPtr wim, IntPtr info); internal wimlib_get_wim_info GetWimInfo; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_get_xml_data( IntPtr wim, ref IntPtr buf_ret, ref UIntPtr bufsize_ret); // size_t internal wimlib_get_xml_data GetXmlData; internal bool IsImageNameInUse(IntPtr wim, string name) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.IsImageNameInUse(wim, name), _ => Utf8.IsImageNameInUse(wim, name), }; } internal int ResolveImage(IntPtr wim, string imageNameOrNum) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.ResolveImage(wim, imageNameOrNum), _ => Utf8.ResolveImage(wim, imageNameOrNum), }; } #endregion #region GetVersion - GetVersion, GetVersionTuple [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate uint wimlib_get_version(); /// /// Return the version of wimlib as a 32-bit number whose top 12 bits contain the /// major version, the next 10 bits contain the minor version, and the low 10 /// bits contain the patch version. /// /// /// In other words, the returned value is equal to ((WIMLIB_MAJOR_VERSION << /// 20) | (WIMLIB_MINOR_VERSION << 10) | WIMLIB_PATCH_VERSION) for the /// corresponding header file. /// internal wimlib_get_version GetVersion; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr wimlib_get_version_string(); internal wimlib_get_version_string GetVersionString; #endregion #region Iterate - IterateDirTree, IterateLookupTable [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] internal delegate int NativeIterateDirTreeCallback( IntPtr dentry, IntPtr progctx); internal int IterateDirTree(IntPtr wim, int image, string path, IterateDirTreeFlags flags, NativeIterateDirTreeCallback cb, IntPtr userCtx) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.IterateDirTree(wim, image, path, flags, cb, userCtx), _ => Utf8.IterateDirTree(wim, image, path, flags, cb, userCtx), }; } [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] internal delegate int NativeIterateLookupTableCallback( ResourceEntry resource, IntPtr progctx); [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] internal delegate int wimlib_iterate_lookup_table( IntPtr wim, IterateLookupTableFlags flags, [MarshalAs(UnmanagedType.FunctionPtr)] NativeIterateLookupTableCallback cb, IntPtr user_ctx); internal wimlib_iterate_lookup_table IterateLookupTable; #endregion #region Join - Join, JoinWithProgress internal ErrorCode Join(string[] swms, uint numSwms, string outputPath, OpenFlags swmsOpenFlags, WriteFlags writeFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.Join(swms, numSwms, outputPath, swmsOpenFlags, writeFlags), _ => Utf8.Join(swms, numSwms, outputPath, swmsOpenFlags, writeFlags), }; } internal ErrorCode JoinWithProgress(string[] swms, uint numSwms, string outputPath, OpenFlags swmsOpenFlags, WriteFlags writeFlags, NativeProgressFunc progfunc, IntPtr progctx) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.JoinWithProgress(swms, numSwms, outputPath, swmsOpenFlags, writeFlags, progfunc, progctx), _ => Utf8.JoinWithProgress(swms, numSwms, outputPath, swmsOpenFlags, writeFlags, progfunc, progctx), }; } #endregion #region Open - OpenWim, OpenWithProgress internal ErrorCode OpenWim(string wimFile, OpenFlags openFlags, out IntPtr wimRet) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.OpenWim(wimFile, openFlags, out wimRet), _ => Utf8.OpenWim(wimFile, openFlags, out wimRet), }; } internal ErrorCode OpenWimWithProgress(string wimFile, OpenFlags openFlags, out IntPtr wimRet, NativeProgressFunc progfunc, IntPtr progctx) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.OpenWimWithProgress(wimFile, openFlags, out wimRet, progfunc, progctx), _ => Utf8.OpenWimWithProgress(wimFile, openFlags, out wimRet, progfunc, progctx), }; } #endregion #region Mount - MountImage (Linux Only) internal ErrorCode MountImage(IntPtr wim, int image, string dir, MountFlags mountFlags, string stagingDir) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.MountImage(wim, image, dir, mountFlags, stagingDir), _ => Utf8.MountImage(wim, image, dir, mountFlags, stagingDir), }; } #endregion #region Reference - ReferenceResourceFiles, ReferenceResources, ReferenceTemplateImage internal ErrorCode ReferenceResourceFiles(IntPtr wim, string[] resourceWimfilesOrGlobs, uint count, RefFlags refFlags, OpenFlags openFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.ReferenceResourceFiles(wim, resourceWimfilesOrGlobs, count, refFlags, openFlags), _ => Utf8.ReferenceResourceFiles(wim, resourceWimfilesOrGlobs, count, refFlags, openFlags), }; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_reference_resources( IntPtr wim, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] resource_wims, uint num_resource_wims, RefFlags ref_flags); internal wimlib_reference_resources ReferenceResources; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_reference_template_image( IntPtr wim, int new_image, IntPtr template_wim, int template_image, int flags); internal wimlib_reference_template_image ReferenceTemplateImage; #endregion #region Rename - RenamePath internal ErrorCode RenamePath(IntPtr wim, int image, string sourcePath, string destPath) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.RenamePath(wim, image, sourcePath, destPath), _ => Utf8.RenamePath(wim, image, sourcePath, destPath), }; } #endregion #region SetImageInfo - SetImageDescription, SetImageFlags, SetImageName, SetImageProperty, SetWimInfo internal ErrorCode SetImageDescription(IntPtr wim, int image, string description) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.SetImageDescription(wim, image, description), _ => Utf8.SetImageDescription(wim, image, description), }; } internal ErrorCode SetImageFlags(IntPtr wim, int image, string flags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.SetImageFlags(wim, image, flags), _ => Utf8.SetImageFlags(wim, image, flags), }; } internal ErrorCode SetImageName(IntPtr wim, int image, string name) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.SetImageName(wim, image, name), _ => Utf8.SetImageName(wim, image, name), }; } internal ErrorCode SetImageProperty(IntPtr wim, int image, string propertyName, string propertyValue) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.SetImageProperty(wim, image, propertyName, propertyValue), _ => Utf8.SetImageProperty(wim, image, propertyName, propertyValue), }; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_wim_info( IntPtr wim, WimInfo info, ChangeFlags which); internal wimlib_set_wim_info SetWimInfo; #endregion #region SetOutput - SetOutputChunkSize, SetOutputPackChunkSize, SetOutputCompressionType, SetOutputPackCompressionType [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_output_chunk_size( IntPtr wim, uint chunk_size); internal wimlib_set_output_chunk_size SetOutputChunkSize; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_output_pack_chunk_size( IntPtr wim, uint chunk_size); internal wimlib_set_output_pack_chunk_size SetOutputPackChunkSize; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_output_compression_type( IntPtr wim, CompressionType ctype); internal wimlib_set_output_compression_type SetOutputCompressionType; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_output_pack_compression_type( IntPtr wim, CompressionType ctype); internal wimlib_set_output_pack_compression_type SetOutputPackCompressionType; #endregion #region Split - Split internal ErrorCode Split(IntPtr wim, string swmName, ulong partSize, WriteFlags writeFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.Split(wim, swmName, partSize, writeFlags), _ => Utf8.Split(wim, swmName, partSize, writeFlags), }; } #endregion #region Verify - VerifyWim [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_verify_wim( IntPtr wim, int verify_flags); internal wimlib_verify_wim VerifyWim; #endregion #region Unmount - UnmountImage, UnmountImageWithProgress (Linux Only) internal ErrorCode UnmountImage(string dir, UnmountFlags unmountFlags) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.UnmountImage(dir, unmountFlags), _ => Utf8.UnmountImage(dir, unmountFlags), }; } internal ErrorCode UnmountImageWithProgress(string dir, UnmountFlags unmountFlags, NativeProgressFunc progfunc, IntPtr progctx) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.UnmountImageWithProgress(dir, unmountFlags, progfunc, progctx), _ => Utf8.UnmountImageWithProgress(dir, unmountFlags, progfunc, progctx), }; } #endregion #region Update - UpdateImage [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_update_image_32( IntPtr wim, int image, [MarshalAs(UnmanagedType.LPArray)] UpdateCommand32[] cmds, uint num_cmds, UpdateFlags update_flags); internal wimlib_update_image_32 UpdateImage32; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_update_image_64( IntPtr wim, int image, [MarshalAs(UnmanagedType.LPArray)] UpdateCommand64[] cmds, ulong num_cmds, UpdateFlags update_flags); internal wimlib_update_image_64 UpdateImage64; #endregion #region Write - Write, Overwrite internal ErrorCode Write(IntPtr wim, string path, int image, WriteFlags writeFlags, uint numThreads) { return UnicodeConvention switch { UnicodeConvention.Utf16 => Utf16.Write(wim, path, image, writeFlags, numThreads), _ => Utf8.Write(wim, path, image, writeFlags, numThreads), }; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_overwrite( IntPtr wim, WriteFlags write_flags, uint numThreads); internal wimlib_overwrite Overwrite; #endregion #region CompressInfo - SetDefaultCompressionLevel, GetCompressorNeededMemory [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_set_default_compression_level( int ctype, uint compression_level); internal wimlib_set_default_compression_level SetDefaultCompressionLevel; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ulong wimlib_get_compressor_needed_memory( CompressionType ctype, UIntPtr max_block_size, // size_t uint compression_level); internal wimlib_get_compressor_needed_memory GetCompressorNeededMemory; #endregion #region Compressor - CreateCompressor, FreeCompressor, Compress [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_create_compressor( CompressionType ctype, UIntPtr max_block_size, // size_t uint compression_level, out IntPtr compressor_ret); internal wimlib_create_compressor CreateCompressor; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void wimlib_free_compressor(IntPtr compressor); internal wimlib_free_compressor FreeCompressor; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal unsafe delegate UIntPtr wimlib_compress( // size_t byte* uncompressed_data, UIntPtr uncompressed_size, // size_t byte* compressed_data, UIntPtr compressed_size_avail, // size_t IntPtr compressor); internal wimlib_compress Compress; #endregion #region Decompressor - CreateDecompressor, FreeDecompressor, Decompress [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate ErrorCode wimlib_create_decompressor( CompressionType ctype, UIntPtr max_block_size, // size_t out IntPtr decompressor_ret); internal wimlib_create_decompressor CreateDecompressor; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void wimlib_free_decompressor(IntPtr decompressor); internal wimlib_free_decompressor FreeDecompressor; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal unsafe delegate int wimlib_decompress( // size_t byte* compressed_data, UIntPtr compressed_size, // size_t byte* uncompressed_data, UIntPtr uncompressed_size_avail, // size_t IntPtr decompressor); internal wimlib_decompress Decompress; #endregion #endregion #region Utility internal static void SetBitField(ref uint bitField, int bitShift, bool value) { if (value) bitField |= (uint)(1 << bitShift); else bitField &= ~(uint)(1 << bitShift); } internal static bool GetBitField(uint bitField, int bitShift) { return (bitField & (1 << bitShift)) != 0; } #endregion } #region enum ErrorPrintState /// /// Represents whether wimlib is printing error messages or not. /// public enum ErrorPrintState { /// /// Error messages are not being printed to ErrorFile. /// PrintOff = 0, /// /// Error messages are being printed to ErrorFile. /// PrintOn = 1, /// /// wimlib was not built with --without-error-messages option. /// NotSupported = 2, } #endregion } ================================================ FILE: ManagedWimLib/WimStruct.cs ================================================ /* Licensed under LGPLv3 Derived from wimlib's original header files Copyright (C) 2012-2018 Eric Biggers C# Wrapper written by Hajin Jang Copyright (C) 2017-2020 Hajin Jang This file is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this file; if not, see http://www.gnu.org/licenses/. */ using Joveler.DynLoader; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; namespace ManagedWimLib { public class Wim : IDisposable { // Wrapper of WIMStruct and wimlib API #region (static) LoadManager internal static WimLibLoadManager Manager = new WimLibLoadManager(); internal static WimLibLoader Lib => Manager.Lib; internal static object _libLock = new object(); #endregion #region (static) GlobalInit, GlobalCleanup /// /// Load system default wimlib library. /// public static void GlobalInit() => GlobalInit(InitFlags.None); /// /// Load system default wimlib library with given flags. /// /// Flags to be passed to wimlib. public static void GlobalInit(InitFlags flags) { lock (_libLock) { Manager.GlobalInit(); Lib.GlobalInit(flags); } } /// /// Load given wimlib library. /// /// The path to the libary file. public static void GlobalInit(string libPath) => GlobalInit(libPath, InitFlags.None); /// /// Load given wimlib library with given flags. /// /// The path to the libary file. /// Flags to be passed to wimlib. public static void GlobalInit(string libPath, InitFlags flags) { lock (_libLock) { Manager.GlobalInit(libPath); Lib.GlobalInit(flags); } } /// /// Cleanup loaded wimlib library. /// Throws InvalidOperationException when the wimlib library was not loaded. /// public static void GlobalCleanup() { lock (_libLock) { if (Lib.GlobalCleanup == null) throw new InvalidOperationException("Please load wimlib library first."); Lib.GlobalCleanup(); Manager.GlobalCleanup(); } } /// /// Cleanup loaded wimlib library. /// Returns queitly when the wimlib lirary was not loaded. /// /// /// Returns true when the library was successfully unloaded. /// public static bool TryGlobalCleanup() { lock (_libLock) { if (Lib.GlobalCleanup == null) return false; Lib.GlobalCleanup(); return Manager.TryGlobalCleanup(); } } #endregion #region Const public const int NoImage = 0; public const int AllImages = -1; /// /// Let wimlib determine best thread count to use. /// public const int DefaultThreads = 0; public const int IterateCallbackSuccess = 0; /// /// A string containing a single path separator; use this to specify the root directory of a WIM image. /// public static string RootPath => Path.DirectorySeparatorChar.ToString(); public const uint DefaultCompressionLevel = 0; private const int EveryCompressionType = -1; #endregion #region Fields private IntPtr _ptr; private ManagedProgressCallback _managedCallback; #endregion #region Properties /// /// The error file which wimlib prints error message to. Valid only if ErrorPrintState is PrintOn, else the property returns null. /// public static string ErrorFile => Lib.GetErrorFilePath(); /// /// Represents whether wimlib is printing error messages or not. /// public static ErrorPrintState ErrorPrintState => Lib.GetErrorPrintState(); #endregion #region Constructor (private) private Wim(IntPtr ptr) { Manager.EnsureLoaded(); _ptr = ptr; } #endregion #region Disposable Pattern ~Wim() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposing) return; if (_ptr == IntPtr.Zero) return; RegisterCallback(null, null); Lib.Free(_ptr); _ptr = IntPtr.Zero; } #endregion #region Error - (Static) GetErrorString, GetErrors, GetLastError, SetPrintErrors /// /// Convert a wimlib error code into a string describing it. /// /// An error code returned by one of wimlib's functions. /// /// string describing the error code. /// If the value was unrecognized, then the resulting string will be "Unknown error". /// public static string GetErrorString(ErrorCode code) { Manager.EnsureLoaded(); IntPtr ptr = Lib.GetErrorString(code); return Lib.PtrToStringAuto(ptr); } /// /// Returns a list of every error messages generated. /// If error logging was turned off by null is returned. /// /// /// Calling this method does not clear old error messages. /// Call to clear them. /// /// An array of error string. If was called, null is returned. public static string[] GetErrors() { Manager.EnsureLoaded(); return Lib.GetErrors(); } /// /// Returns last error message. /// If error logging was turned off by , null is returned. /// /// /// Calling this method does not clear old error messages. /// Call to clear them. /// /// /// If error had been created, an error string is returned. /// If error had not been generated or was called, null is returned. /// public static string GetLastError() { Manager.EnsureLoaded(); return Lib.GetLastError(); } /// /// Clear old error messages. /// public static void ResetErrorFile() { Manager.EnsureLoaded(); Lib.ResetErrorFile(); } /// /// Set whether wimlib can print error and warning messages to the error file, which can be retreived with . /// Error and warning messages may provide information that cannot be determined only from returned error codes. /// /// This setting applies globally (not per-WIM). /// This can be called before . /// /// /// true if messages are to be printed; /// false if messages are not to be printed. /// /// wimlib did not return . public static void SetPrintErrors(bool showMessages) { Manager.EnsureLoaded(); Lib.SetPrintErrors(showMessages); } #endregion #region Add - AddEmptyImage, AddImage, AddImageMultiSource, AddTree /// /// Append an empty image to a . /// /// The new image will initially contain no files or directories, although if written without further modifications, /// then a root directory will be created automatically for it. /// /// /// After calling this function, you can use to add files to the new image. /// This gives you more control over making the new image compared to calling or . /// /// /// Name to give the new image. /// If null or empty, the new image is given no name. /// If nonempty, it must specify a name that does not already exist in wim. /// /// If non-null, the index of the newly added image is returned in this location. /// wimlib did not return . public int AddEmptyImage(string name) { ErrorCode ret = Lib.AddEmptyImage(_ptr, name, out int newIdx); WimLibException.CheckErrorCode(ret); return newIdx; } /// /// Add an image to a from an on-disk directory tree or NTFS volume. /// /// /// A path to a directory or unmounted NTFS volume that will be captured as a WIM image. /// /// /// Name to give the new image. /// If null or empty, the new image is given no name. /// If nonempty, it must specify a name that does not already exist in wim. /// /// /// Path to capture configuration file, or null. /// This file may specify, among other things, which files to exclude from capture. /// /// If null, the default capture configuration will be used. /// Ordinarily, the default capture configuration will result in no files being excluded from capture purely based on name; /// however, the and flags modify the default. /// /// /// Bitwise OR of . /// /// /// The directory tree or NTFS volume is scanned immediately to load the dentry tree into memory, and file metadata is read. /// However, actual file data may not be read until the is persisted to disk using or . /// /// See the documentation for the wimlib-imagex program for more information /// about the "normal" capture mode versus the NTFS capture mode (entered by providing the flag ). /// /// Note that no changes are committed to disk until or is called. /// /// wimlib did not return . public void AddImage(string source, string name, string configFile, AddFlags addFlags) { ErrorCode ret = Lib.AddImage(_ptr, source, name, configFile, addFlags); WimLibException.CheckErrorCode(ret); } /// /// Equivalent to except it allows for multiple sources to be combined into a single WIM image. /// /// /// Array of capture sources. /// /// /// Name to give the new image. /// If null or empty, the new image is given no name. /// If nonempty, it must specify a name that does not already exist in wim. /// /// /// Path to capture configuration file, or null. /// This file may specify, among other things, which files to exclude from capture. /// /// If null, the default capture configuration will be used. /// Ordinarily, the default capture configuration will result in no files being excluded from capture purely based on name; /// however, the and flags modify the default. /// /// Bitwise OR of . /// wimlib did not return . public void AddImageMultiSource(IEnumerable sources, string name, string configFile, AddFlags addFlags) { CaptureSource[] srcArr = sources.ToArray(); ErrorCode ret = Lib.AddImageMultiSource(_ptr, srcArr, new UIntPtr((uint)srcArr.Length), name, configFile, addFlags); WimLibException.CheckErrorCode(ret); } /// /// Add the file or directory tree at on the filesystem to the location /// within the specified image of the wim. /// /// This just builds an appropriate and passes it to . /// /// /// /// /// Bitwise OR of . /// /// wimlib did not return . public void AddTree(int image, string fsSourcePath, string wimTargetPath, AddFlags addFlags) { ErrorCode ret = Lib.AddTree(_ptr, image, fsSourcePath, wimTargetPath, addFlags); WimLibException.CheckErrorCode(ret); } #endregion #region Create - (Static) CreateNewWim /// /// Create a which initially contains no images and is not backed by an on-disk file. /// /// /// The "output compression type" to assign to the . /// This is the compression type that will be used if the is later persisted to an on-disk file using . /// /// wimlib did not return . public static Wim CreateNewWim(CompressionType type) { Manager.EnsureLoaded(); ErrorCode ret = Lib.CreateNewWim(type, out IntPtr wimPtr); WimLibException.CheckErrorCode(ret); return new Wim(wimPtr); } #endregion #region Delete - DeleteImage, DeletePath /// /// Delete an image, or all images, from a . /// /// /// Note that no changes are committed to disk until or is called. /// /// The 1-based index of the image to delete, or to delete all images. /// wimlib did not return . public void DeleteImage(int image) { ErrorCode ret = Lib.DeleteImage(_ptr, image); WimLibException.CheckErrorCode(ret); } /// /// Delete the path from the specified image of the wim. /// /// /// This just builds an appropriate and passes it to . /// /// The 1-based index of the image to delete, or to delete all images. /// Path to be deleted from the specified image of the wim. /// Bitwise OR of . /// wimlib did not return . public void DeletePath(int image, string path, DeleteFlags deleteFlags) { ErrorCode ret = Lib.DeletePath(_ptr, image, path, deleteFlags); WimLibException.CheckErrorCode(ret); } #endregion #region Export - ExportImage /// /// Export an image, or all images, from a into another . /// /// Specifically, if the destination contains n images, then /// the source image(s) will be appended, in order, starting at destination index n + 1. /// By default, all image metadata will be exported verbatim, but certain changes can be made by passing appropriate parameters. /// /// The 1-based index of the image from src_wim to export, or /// The to which to export the images. /// /// For single-image exports, the name to give the exported image in destWim. /// If left null, the name from srcWim is used. /// For exports, this parameter must be left null; in that case, the names are all taken from src_wim. /// This parameter is overridden by . /// /// For single-image exports, the description to give the exported image in the new WIM file. /// If left null, the description from src_wim is used. /// For exports, this parameter must be left null; in that case, the description are all taken from src_wim. /// This parameter is overridden by . /// /// Bitwise OR of flags with . /// /// is only an in-memory operation; /// no changes are committed to disk until or is called. /// /// A limitation of the current implementation of is that /// the directory tree of a source or destination image cannot be updated /// following an export until one of the two images has been freed from memory. /// /// wimlib did not return . public void ExportImage(int srcImage, Wim destWim, string destName, string destDescription, ExportFlags exportFlags) { ErrorCode ret = Lib.ExportImage(_ptr, srcImage, destWim._ptr, destName, destDescription, exportFlags); WimLibException.CheckErrorCode(ret); } #endregion #region Extract - ExtractImage, ExtractPath, ExtractPaths, ExtractPathList /// /// Extract an image, or all images, from a . /// /// /// The 1-based index of the image to extract, or to extract all images. /// Note: is unsupported in NTFS-3G extraction mode. /// /// /// A null-terminated string which names the location to which the image(s) will be extracted. /// By default, this is interpreted as a path to a directory. /// Alternatively, if is specified in extractFlags, /// then this is interpreted as a path to an unmounted NTFS volume. /// /// /// Bitwise OR of ExtractFlags. /// /// /// The exact behavior of how wimlib extracts files from a WIM image is controllable by the , /// but there also are differences depending on the platform (UNIX-like vs Windows). /// See the documentation for wimapply for more information, including about the NTFS-3G extraction mode. /// /// wimlib did not return . public void ExtractImage(int image, string target, ExtractFlags extractFlags) { ErrorCode ret = Lib.ExtractImage(_ptr, image, target, extractFlags); WimLibException.CheckErrorCode(ret); } /// /// Extract one path (files or directory trees) from the specified WIM image. /// /// /// By default, each path will be extracted to a corresponding subdirectory of the target based on its location in the image. /// For example, if one of the paths to extract is /Windows/explorer.exe and the target is outdir, /// the file will be extracted to outdir/Windows/explorer.exe. /// This behavior can be changed by providing the flag , /// which will cause each file or directory tree to be placed directly in the target directory /// --- so the same example would extract /Windows/explorer.exe to outdir/explorer.exe. /// /// With globbing turned off (the default), paths are always checked for existence strictly; /// that is, if any path to extract does not exist in the image, then nothing is extracted /// and the function fails with . /// But with globbing turned on ( specified), globs are by default permitted to match no files, /// and there is a flag () to enable the strict behavior if desired. /// /// Symbolic links are not dereferenced when paths in the image are interpreted. /// /// /// The 1-based index of the WIM image from which to extract the paths. /// /// /// Directory to which to extract the paths. /// /// /// Path to extract, must be the absolute path to a file or directory within the image. /// Path separators may be either forwards or backwards slashes, and leading path separators are optional. /// The paths will be interpreted either case-sensitively (UNIX default) or case-insensitively (Windows default); /// however, the case sensitivity can be configured explicitly at library initialization time by passing an /// appropriate flag to . /// /// /// Bitwise OR of flags prefixed with . /// /// wimlib did not return . public void ExtractPath(int image, string target, string path, ExtractFlags extractFlags) { ErrorCode ret = Lib.ExtractPaths(_ptr, image, target, new string[1] { path }, new UIntPtr(1), extractFlags); WimLibException.CheckErrorCode(ret); } /// /// Extract zero or more paths (files or directory trees) from the specified WIM image. /// /// /// By default, each path will be extracted to a corresponding subdirectory of the target based on its location in the image. /// For example, if one of the paths to extract is /Windows/explorer.exe and the target is outdir, /// the file will be extracted to outdir/Windows/explorer.exe. /// This behavior can be changed by providing the flag ExtractFlags.NO_PRESERVE_DIR_STRUCTURE, /// which will cause each file or directory tree to be placed directly in the target directory /// --- so the same example would extract /Windows/explorer.exe to outdir/explorer.exe. /// /// With globbing turned off (the default), paths are always checked for existence strictly; /// that is, if any path to extract does not exist in the image, then nothing is extracted /// and the function fails with ErrorCode.PATH_DOES_NOT_EXIST. /// But with globbing turned on (ExtractFlags.GLOB_PATHS specified), globs are by default permitted to match no files, /// and there is a flag (ExtractFlags.STRICT_GLOB) to enable the strict behavior if desired. /// /// Symbolic links are not dereferenced when paths in the image are interpreted. /// /// /// The 1-based index of the WIM image from which to extract the paths. /// /// /// Directory to which to extract the paths. /// /// /// Array of paths to extract. /// Each element must be the absolute path to a file or directory within the image. /// Path separators may be either forwards or backwards slashes, and leading path separators are optional. /// The paths will be interpreted either case-sensitively (UNIX default) or case-insensitively (Windows default); /// however, the case sensitivity can be configured explicitly at library initialization time by passing an /// appropriate flag to AssemblyInit(). /// /// /// Bitwise OR of flags prefixed with WIMLIB_EXTRACT_FLAG. /// /// wimlib did not return . public void ExtractPaths(int image, string target, IEnumerable paths, ExtractFlags extractFlags) { string[] pathArr = paths.ToArray(); ErrorCode ret = Lib.ExtractPaths(_ptr, image, target, pathArr, new UIntPtr((uint)pathArr.Length), extractFlags); WimLibException.CheckErrorCode(ret); } /// /// Similar to ExtractPaths(), but the paths to extract from the . /// image are specified in the ASCII, UTF-8, or UTF-16LE text file named by /// which itself contains the list of paths to use, one per line. /// /// /// Leading and trailing whitespace is ignored. /// Empty lines and lines beginning with the ';' or '#' characters are ignored. /// No quotes are needed, as paths are otherwise delimited by the newline character. /// However, quotes will be stripped if present. /// /// If is null, then the pathlist file is read from standard input. /// /// wimlib did not return . public void ExtractPathList(int image, string target, string pathListFile, ExtractFlags extractFlags) { ErrorCode ret = Lib.ExtractPathList(_ptr, image, target, pathListFile, extractFlags); WimLibException.CheckErrorCode(ret); } #endregion #region GetImageInfo - GetImageDescription, GetImageName, GetImageProperty /// /// Get the description of the specified image. /// Equivalent to GetImageProperty(image, "DESCRIPTION"). /// /// The 1-based index of the image for which to set the property. public string GetImageDescription(int image) { IntPtr ptr = Lib.GetImageDescription(_ptr, image); return ptr == IntPtr.Zero ? null : Lib.PtrToStringAuto(ptr); } /// /// Get the name of the specified image. /// Similar to GetImageProperty(image, "NAME"). /// /// The 1-based index of the image for which to set the property. /// /// will return an empty string if the image is unnamed /// whereas may return null in that case. /// public string GetImageName(int image) { IntPtr ptr = Lib.GetImageName(_ptr, image); return ptr == IntPtr.Zero ? null : Lib.PtrToStringAuto(ptr); } /// /// Since wimlib v1.8.3: get a per-image property from the WIM's XML document. /// /// /// This is an alternative to and /// which allows getting any simple string property. /// /// The 1-based index of the image for which to set the property. /// /// The name of the image property, for example "NAME", "DESCRIPTION", or "TOTALBYTES". /// The name can contain forward slashes to indicate a nested XML element; for example, "WINDOWS/VERSION/BUILD" /// indicates the BUILD element nested within the VERSION element nested within the WINDOWS element. /// Since wimlib v1.9.0, a bracketed number can be used to indicate one of several identically-named elements; for example, /// "WINDOWS/LANGUAGES/LANGUAGE[2]" indicates the second "LANGUAGE" element nested within the "WINDOWS/LANGUAGES" element. /// Note that element names are case sensitive. /// /// /// The property's value as a string, or null if there is no such property. /// public string GetImageProperty(int image, string propertyName) { IntPtr ptr = Lib.GetImageProperty(_ptr, image, propertyName); return ptr == IntPtr.Zero ? null : Lib.PtrToStringAuto(ptr); } #endregion #region GetWimInfo - GetWimInfo, GetXmlData, IsImageNameInUse, ResolveImage /// /// Get basic information about a WIM file. /// public WimInfo GetWimInfo() { IntPtr infoPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { // This function always return 0, so no need to check exception. Lib.GetWimInfo(_ptr, infoPtr); return Marshal.PtrToStructure(infoPtr); } finally { Marshal.FreeHGlobal(infoPtr); } } /// /// Read a WIM file's XML document into an in-memory buffer. /// /// The XML document contains metadata about the WIM file and the images stored in it. /// /// string contains XML document is returned. public string GetXmlData() { IntPtr buffer = IntPtr.Zero; UIntPtr bufferSize = UIntPtr.Zero; Lib.GetXmlData(_ptr, ref buffer, ref bufferSize); // bufferSize is a length in byte. // Marshal.PtrStringUni expects length of characters. // Since xml is returned in UTF-16LE, divide by two. int charLen = (int)(bufferSize.ToUInt32() / 2); return Marshal.PtrToStringUni(buffer, charLen).Trim(); } /// /// Determine if an image name is already used by some image in the WIM. /// /// The name to check. /// /// true if there is already an image in wim named name; /// false if there is no image named name in wim. /// If name is null or the empty string, then false is returned. /// public bool IsImageNameInUse(string name) { return Lib.IsImageNameInUse(_ptr, name); } /// /// Translate a string specifying the name or number of an image in the WIM into the number of the image. /// The images are numbered starting at 1. /// /// /// A string specifying the name or number of an image in the WIM. /// If it parses to a positive integer, this integer is taken to specify the number of the image, indexed starting at 1. /// Otherwise, it is taken to be the name of an image, as given in the XML data for the WIM file. /// It also may be the keyword "all" or the string "*", both of which will resolve to . /// /// /// If the string resolved to a single existing image, the number of that image, indexed starting at 1, is returned. /// If the keyword "all" or "*" was specified, is returned. /// Otherwise, is returned. /// /// If was null or the empty string, is returned, even if one or more images in wim has no name. /// (Since a WIM may have multiple unnamed images, an unnamed image must be specified by index to eliminate the ambiguity.) /// public int ResolveImage(string imageNameOrNum) { return Lib.ResolveImage(_ptr, imageNameOrNum); } #endregion #region GetVersion - (Static) GetVersion, GetVersionString /// /// Return the version of wimlib as a Version instance. /// Major, Minor and Build (Patch) properties will be populated. /// public static Version Version { get { Manager.EnsureLoaded(); uint dword = Lib.GetVersion(); ushort major = (ushort)(dword >> 20); ushort minor = (ushort)((dword % (1 << 20)) >> 10); ushort patch = (ushort)(dword % (1 << 10)); return new Version(major, minor, patch); } } /// /// Since wimlib v1.13.0: like , but returns the full PACKAGE_VERSION string that was set at build time. /// (This allows a beta release to be distinguished from an official release.) /// public static string VersionStr { get { Manager.EnsureLoaded(); IntPtr ptr = Lib.GetVersionString(); return Lib.PtrToStringAuto(ptr); } } #endregion #region Iterate - IterateDirTree, IterateLookupTable /// /// Iterate through a file or directory tree in a WIM image. /// By specifying appropriate flags and a callback function, you can get the attributes of a /// file in the image, get a directory listing, or even get a listing of the entire image. /// /// /// The 1-based index of the image that contains the files or directories to iterate over, /// or to iterate over all images. /// /// /// Path in the image at which to do the iteration. /// /// /// Bitwise OR of . /// /// /// A callback function that will receive each directory entry. /// /// /// Normally, returns 0 if all calls to callback returned 0; otherwise the first nonzero value that was returned from callback. /// /// /// One of the values returned, including the following: /// : image does not exist. /// : does not exist in the image. /// : was specified, /// but the data for some files could not be found in the blob lookup table. /// /// This function can additionally return , /// , , /// , or , all of which /// indicate failure (for different reasons) to read the metadata resource for an image over which iteration needed to be done. /// public int IterateDirTree(int image, string path, IterateDirTreeFlags iterateFlags, IterateDirTreeCallback callback) { return IterateDirTree(image, path, iterateFlags, callback, null); } /// /// Iterate through a file or directory tree in a WIM image. /// By specifying appropriate flags and a callback function, you can get the attributes of a /// file in the image, get a directory listing, or even get a listing of the entire image. /// /// /// The 1-based index of the image that contains the files or directories to iterate over, /// or to iterate over all images. /// /// /// Path in the image at which to do the iteration. /// /// /// Bitwise OR of . /// /// /// A callback function that will receive each directory entry. /// /// /// An extra parameter that will always be passed to the callback function. /// /// /// Normally, returns 0 if all calls to callback returned 0; otherwise the first nonzero value that was returned from . /// /// /// One of the values returned, including the following: /// : image does not exist. /// : does not exist in the image. /// : was specified, /// but the data for some files could not be found in the blob lookup table. /// /// This function can additionally return , /// , , /// , or , all of which /// indicate failure (for different reasons) to read the metadata resource for an image over which iteration needed to be done. /// public int IterateDirTree(int image, string path, IterateDirTreeFlags iterateFlags, IterateDirTreeCallback callback, object userData) { ManagedIterateDirTreeCallback cb = new ManagedIterateDirTreeCallback(callback, userData); int ret = Lib.IterateDirTree(_ptr, image, path, iterateFlags, cb.NativeFunc, IntPtr.Zero); // This is a list of every possible ErrorCode that Lib.IterateDirTree may return. switch (ret) { // Image does not exist in Wim. case (int)ErrorCode.InvalidImage: // Path does not exist in the image. case (int)ErrorCode.PathDoesNotExist: // IterateFlags.ResourcesNeeded was specified, but the data for some files could not be found in the blob lookup table of Wim. case (int)ErrorCode.ResourceNotFound: // These 5 error codes indicate failure to read the metadata resource for an image over which iteration needed to be done. case (int)ErrorCode.Decompression: case (int)ErrorCode.InvalidMetadataResource: case (int)ErrorCode.MetadataNotFound: case (int)ErrorCode.Read: case (int)ErrorCode.UnexpectedEndOfFile: WimLibException.CheckErrorCode((ErrorCode)ret); break; } return ret; } /// /// Special variant of which does not throw an exception. /// Instead, it returns directly. /// /// /// The 1-based index of the image that contains the files or directories to iterate over, /// or to iterate over all images. /// /// /// Path in the image at which to do the iteration. /// /// /// Bitwise OR of . /// /// /// A callback function that will receive each directory entry. /// /// /// Error code returned by wimlib. If no error had occured, is returned. /// /// /// Normally, returns 0 if all calls to callback returned 0; otherwise the first nonzero value that was returned from callback. /// public int IterateDirTree(int image, string path, IterateDirTreeFlags iterateFlags, IterateDirTreeCallback callback, out ErrorCode errorCode) { return IterateDirTree(image, path, iterateFlags, callback, null, out errorCode); } /// /// Special variant of which does not throw an exception. /// Instead, it returns directly. /// /// /// The 1-based index of the image that contains the files or directories to iterate over, /// or to iterate over all images. /// /// /// Path in the image at which to do the iteration. /// /// /// Bitwise OR of . /// /// /// A callback function that will receive each directory entry. /// /// /// An extra parameter that will always be passed to the callback function. /// /// /// Error code returned by wimlib. If no error had occured, is returned. /// /// /// Normally, returns 0 if all calls to callback returned 0; otherwise the first nonzero value that was returned from . /// public int IterateDirTree(int image, string path, IterateDirTreeFlags iterateFlags, IterateDirTreeCallback callback, object userData, out ErrorCode errorCode) { errorCode = ErrorCode.Success; ManagedIterateDirTreeCallback cb = new ManagedIterateDirTreeCallback(callback, userData); int ret = Lib.IterateDirTree(_ptr, image, path, iterateFlags, cb.NativeFunc, IntPtr.Zero); // This is a list of every possible ErrorCode that Lib.IterateDirTree may return. switch (ret) { // Image does not exist in Wim. case (int)ErrorCode.InvalidImage: // Path does not exist in the image. case (int)ErrorCode.PathDoesNotExist: // IterateFlags.ResourcesNeeded was specified, but the data for some files could not be found in the blob lookup table of Wim. case (int)ErrorCode.ResourceNotFound: // These 5 error codes indicate failure to read the metadata resource for an image over which iteration needed to be done. case (int)ErrorCode.Decompression: case (int)ErrorCode.InvalidMetadataResource: case (int)ErrorCode.MetadataNotFound: case (int)ErrorCode.Read: case (int)ErrorCode.UnexpectedEndOfFile: errorCode = (ErrorCode)ret; break; } return ret; } /// /// Iterate through the blob lookup table of a . /// This can be used to directly get a listing of the unique "blobs" contained in a WIM file, /// which are deduplicated over all images. /// /// /// Specifically, each listed blob may be from any of the following sources: /// /// - Metadata blobs, if the contains image metadata /// - File blobs from the on-disk WIM file(if any) backing the /// - File blobs from files that have been added to the in-memory , e.g.by using /// - File blobs from external WIMs referenced by or /// /// Reserved; set to /// A callback function that will receive each blob. /// wimlib did not return . /// /// Normally, returns 0 if all calls to callback returned 0; otherwise the first nonzero value that was returned from . /// public int IterateLookupTable(IterateLookupTableFlags flags, IterateLookupTableCallback callback) { return IterateLookupTable(flags, callback, null); } /// /// Iterate through the blob lookup table of a . /// This can be used to directly get a listing of the unique "blobs" contained in a WIM file, /// which are deduplicated over all images. /// /// Reserved; set to /// A callback function that will receive each blob. /// An extra parameter that will always be passed to the callback function /// /// Normally, returns 0 if all calls to callback returned 0; otherwise the first nonzero value that was returned from . /// public int IterateLookupTable(IterateLookupTableFlags flags, IterateLookupTableCallback callback, object userData) { ManagedIterateLookupTableCallback cb = new ManagedIterateLookupTableCallback(callback, userData); return Lib.IterateLookupTable(_ptr, flags, cb.NativeFunc, IntPtr.Zero); } #endregion #region Join - (Static) Join /// /// Join a split WIM into a stand-alone (one-part) WIM. /// /// /// Note: wimlib is generalized enough that this function is not actually needed to join a split WIM; /// instead, you could open the first part of the split WIM, then reference the other parts with , /// thn write the joined WIM using . /// However, provide an easy-to-use wrapper around this that has some advantages (e.g. extra sanity checks). /// /// /// An array of strings that gives the filenames of all parts of the split WIM. /// No specific order is required, but all parts must be included with no duplicates. /// /// The path to write the joined WIM file to. /// Open flags for the split WIM parts (e.g. ). /// Bitwise OR of relevant , which will be used to write the joined WIM. /// wimlib did not return . public static void Join(IEnumerable swms, string outputPath, OpenFlags swmOpenFlags, WriteFlags wimWriteFlags) { string[] swmArr = swms.ToArray(); ErrorCode ret = Lib.Join(swmArr, (uint)swmArr.Length, outputPath, swmOpenFlags, wimWriteFlags); WimLibException.CheckErrorCode(ret); } /// /// Same as , but allows specifying a progress function. /// /// /// The progress function will receive the write progress messages, such as , while writing the joined WIM. /// In addition, if is specified in swmOpenFlags, the progress function will receive a series of /// messages when each of the split WIM parts is opened. /// /// /// An array of strings that gives the filenames of all parts of the split WIM. /// No specific order is required, but all parts must be included with no duplicates. /// /// The path to write the joined WIM file to. /// Open flags for the split WIM parts (e.g. ). /// Bitwise OR of relevant , which will be used to write the joined WIM. /// Callback function to receive progress report /// wimlib did not return . public static void Join(IEnumerable swms, string outputPath, OpenFlags swmOpenFlags, WriteFlags wimWriteFlags, ProgressCallback callback) { Join(swms, outputPath, swmOpenFlags, wimWriteFlags, callback, null); } /// /// Same as , but allows specifying a progress function. /// /// /// The progress function will receive the write progress messages, such as , while writing the joined WIM. /// In addition, if is specified in swmOpenFlags, the progress function will receive a series of /// messages when each of the split WIM parts is opened. /// /// /// An array of strings that gives the filenames of all parts of the split WIM. /// No specific order is required, but all parts must be included with no duplicates. /// /// The path to write the joined WIM file to. /// Open flags for the split WIM parts (e.g. ). /// Bitwise OR of relevant , which will be used to write the joined WIM. /// Callback function to receive progress report /// Data to be passed to callback function /// wimlib did not return . public static void Join(IEnumerable swms, string outputPath, OpenFlags swmOpenFlags, WriteFlags wimWriteFlags, ProgressCallback callback, object userData) { ManagedProgressCallback mCallback = new ManagedProgressCallback(callback, userData); string[] swmArr = swms.ToArray(); ErrorCode ret = Lib.JoinWithProgress(swmArr, (uint)swmArr.Length, outputPath, swmOpenFlags, wimWriteFlags, mCallback.NativeFunc, IntPtr.Zero); WimLibException.CheckErrorCode(ret); } #endregion #region Open - (Static) OpenWim /// /// Open a WIM file and create a instance of Wim class for it. /// /// The path to the WIM file to open. /// Bitwise OR of flags prefixed with . /// /// On success, a new instance of backed by the specified on-disk WIM file is returned. /// This instance must be disposed when finished with it. /// /// wimlib did not return . public static Wim OpenWim(string wimFile, OpenFlags openFlags) { Manager.EnsureLoaded(); ErrorCode ret = Lib.OpenWim(wimFile, openFlags, out IntPtr wimPtr); WimLibException.CheckErrorCode(ret); return new Wim(wimPtr); } /// /// Same as , but allows specifying a progress function and progress context. /// /// /// If successful, the progress function will be registered in the newly open , /// as if by an automatic call to . /// /// In addition, if is specified in openFlags, /// then the callback function will receive messages while checking the WIM file's integrity. /// /// The path to the WIM file to open. /// Bitwise OR of flags prefixed with . /// Callback function to receive progress report /// Data to be passed to callback function /// /// On success, a new instance of backed by the specified on-disk WIM file is returned. /// This instance must be disposed when finished with it. /// /// wimlib did not return . public static Wim OpenWim(string wimFile, OpenFlags openFlags, ProgressCallback callback, object userData = null) { Manager.EnsureLoaded(); if (callback == null) throw new ArgumentNullException(nameof(callback)); ManagedProgressCallback mCallback = new ManagedProgressCallback(callback, userData); ErrorCode ret = Lib.OpenWimWithProgress(wimFile, openFlags, out IntPtr wimPtr, mCallback.NativeFunc, IntPtr.Zero); WimLibException.CheckErrorCode(ret); return new Wim(wimPtr) { _managedCallback = mCallback }; } #endregion #region Mount - MountImage (Linux with FUSE only) /// /// Mount an image from a WIM file on a directory read-only or read-write. /// /// The ability to mount WIM images is implemented using FUSE. /// Depending on how FUSE is set up on your system, this function /// may work as normal users in addition to the root user. /// /// Calling this function daemonizes the process, unless /// was specified or an early error occurs. /// /// /// Mounting WIM images is not supported if wimlib was configured --without-fuse. /// This includes Windows builds of wimlib; will be returned in such cases. /// /// It is safe to mount multiple images from the same WIM file read-only at the /// same time, but only if different Wim instances' are used. /// It is not safe to mount multiple images from the same WIM file read-write at the same time. /// /// To unmount the image, call . This may be done in a different process. /// /// /// The 1-based index of the image to mount. /// This image cannot have been previously modified in memory. /// /// /// The path to an existing empty directory on which to mount the image. /// /// /// Bitwise OR of . /// Use to request a read-write mount instead of a read-only mount /// /// /// If non-null, the name of a directory in which a temporary directory for storing modified or /// added files will be created. Ignored if is not specified in mountFlags. /// If left null, the staging directory is created in the same directory as the backing WIM file. /// The staging directory is automatically deleted when the image is unmounted. /// /// wimlib did not return . public void MountImage(int image, string dir, MountFlags mountFlags, string stagingDir) { ErrorCode ret = Lib.MountImage(_ptr, image, dir, mountFlags, stagingDir); WimLibException.CheckErrorCode(ret); } #endregion #region Reference - ReferenceResourceFiles, ReferenceResources, ReferenceTemplateImage /// /// Reference file data from other WIM files or split WIM parts. /// This function can be used on WIMs that are not standalone, such as split or "delta" WIMs, /// to load additional file data before calling a function such as that requires the file data to be present. /// /// /// In the case of split WIMs, instance of should be the/first part, since only the first part contains the metadata resources. /// In the case of delta WIMs, this should be the delta WIM rather than the WIM on which it is based. /// /// /// A path to WIM file and/or split WIM parts to reference. /// Alternatively, when is specified in refFlags, these are treated as globs rather than literal paths. /// That is, using this function you can specify zero or more globs, each of which expands to one or more literal paths. /// /// /// Bitwise OR of RefFlags. GLOB_ENABLE and/or . /// /// /// Additional open flags, such as , /// to pass to internal calls to on the reference files. /// /// wimlib did not return . public void ReferenceResourceFile(string resourceWimFileOrGlobs, RefFlags refFlags, OpenFlags openFlags) { if (resourceWimFileOrGlobs == null) throw new ArgumentNullException(nameof(resourceWimFileOrGlobs)); // Old ManagedWimLib had been using a hack which emulates GLOBing by converting wildcard to list of actual files before calling wimlib. // If ReferenceResourceFiles() is called with RefFlags.GlobEnable | RefFlags.GlobErrOnNoMatch in DEBUG mode on Windows, SEHException had raised. // But the hack is no longer necessary starting from wimlib 1.13.3. // Ref: wimlib 1.13.3 release note(https://wimlib.net/forums/viewtopic.php?f=1&t=543) string[] resources = new string[1] { resourceWimFileOrGlobs }; ErrorCode ret = Lib.ReferenceResourceFiles(_ptr, resources, 1u, refFlags, openFlags); WimLibException.CheckErrorCode(ret); } /// /// Reference file data from other WIM files or split WIM parts. /// This function can be used on WIMs that are not standalone, such as split or "delta" WIMs, /// to load additional file data before calling a function such as /// that requires the file data to be present. /// /// /// In the case of split WIMs, instance of should be the/first part, since only the first part contains the metadata resources. /// In the case of delta WIMs, this should be the delta WIM rather than the WIM on which it is based. /// /// /// Array of paths to WIM files and/or split WIM parts to reference. /// Alternatively, when is specified in , these are treated as globs rather than literal paths. /// That is, using this function you can specify zero or more globs, each of which expands to one or more literal paths. /// /// /// Bitwise OR of and/or . /// /// /// Additional open flags, such as , /// to pass to internal calls to on the reference files. /// /// wimlib did not return . public void ReferenceResourceFiles(IEnumerable resourceWimFileOrGlobs, RefFlags refFlags, OpenFlags openFlags) { // Old ManagedWimLib had been using a hack which emulates GLOBing by converting wildcard to list of actual files before calling wimlib. // If ReferenceResourceFiles() is called with RefFlags.GlobEnable | RefFlags.GlobErrOnNoMatch in DEBUG mode on Windows, SEHException had raised. // But the hack is no longer necessary starting from wimlib 1.13.3. // Ref: wimlib 1.13.3 release note(https://wimlib.net/forums/viewtopic.php?f=1&t=543) ErrorCode ret = Lib.ReferenceResourceFiles(_ptr, resourceWimFileOrGlobs.ToArray(), (uint)resourceWimFileOrGlobs.Count(), refFlags, openFlags); WimLibException.CheckErrorCode(ret); } /// /// Similar to , but operates at a lower level /// where the caller must open the for each referenced file itself. /// /// Array of pointers to the 's for additional resource WIMs or split WIM parts to reference. public void ReferenceResources(IEnumerable resourceWims) { IntPtr[] wims = resourceWims.Select(x => x._ptr).ToArray(); ErrorCode ret = Lib.ReferenceResources(_ptr, wims, (uint)wims.Length, 0); WimLibException.CheckErrorCode(ret); } /// /// Declare that a newly added image is mostly the same as a prior image, but /// captured at a later point in time, possibly with some modifications in the intervening time. /// This is designed to be used in incremental backups of the same filesystem or directory tree. /// /// The 1-based index in @p wim of the newly added image. /// The 1-based index in @p template_wim of the template image. /// /// This function compares the metadata of the directory tree of the newly added image against that of the old image. /// Any files that are present in both the newly added image and the old image and have timestamps that indicate they /// haven't been modified are deemed not to have been modified and have their checksums copied from the old image. /// Because of this and because WIM uses single-instance streams, such files need not be read from the filesystem when /// the WIM is being written or overwritten. /// Note that these unchanged files will still be "archived" and will be logically present in the new image; /// the optimization is that they don't need to actually be read from the filesystem because the WIM already contains them. /// /// This function is provided to optimize incremental backups. /// The resulting WIM file will still be the same regardless of whether this function is called. /// (This is, however, assuming that timestamps have not been manipulated or /// unmaintained as to trick this function into thinking a file has not been modified when really it has. /// To partly guard against such cases, other metadata such as file sizes will be checked as well.) /// /// This function must be called after adding the new image (e.g. with ), /// but before writing the updated WIM file (e.g. with ). /// /// wimlib did not return . public void ReferenceTemplateImage(int newImage, int templateImage) { ErrorCode ret = Lib.ReferenceTemplateImage(_ptr, newImage, _ptr, templateImage, 0); WimLibException.CheckErrorCode(ret); } /// /// Declare that a newly added image is mostly the same as a prior image, but /// captured at a later point in time, possibly with some modifications in the intervening time. /// This is designed to be used in incremental backups of the same filesystem or directory tree. /// /// The 1-based index in @p wim of the newly added image. /// /// A containing the template image. /// This can be, but does not have to be, the same as instance itself. /// /// The 1-based index in @p template_wim of the template image. /// /// This function compares the metadata of the directory tree of the newly added image against that of the old image. /// Any files that are present in both the newly added image and the old image and have timestamps that indicate they /// haven't been modified are deemed not to have been modified and have their checksums copied from the old image. /// Because of this and because WIM uses single-instance streams, such files need not be read from the filesystem when /// the WIM is being written or overwritten. /// Note that these unchanged files will still be "archived" and will be logically present in the new image; /// the optimization is that they don't need to actually be read from the filesystem because the WIM already contains them. /// /// This function is provided to optimize incremental backups. /// The resulting WIM file will still be the same regardless of whether this function is called. /// (This is, however, assuming that timestamps have not been manipulated or /// unmaintained as to trick this function into thinking a file has not been modified when really it has. /// To partly guard against such cases, other metadata such as file sizes will be checked as well.) /// /// This function must be called after adding the new image (e.g. with ), /// but before writing the updated WIM file (e.g. with ). /// /// wimlib did not return . public void ReferenceTemplateImage(int newImage, Wim template, int templateImage) { ErrorCode ret = Lib.ReferenceTemplateImage(_ptr, newImage, template._ptr, templateImage, 0); WimLibException.CheckErrorCode(ret); } #endregion #region Callback - RegisterCallback /// /// Register a progress function with a . /// /// /// Pointer to the progress function to register. /// If the WIM already has a progress function registered, it will be replaced with this one. /// If null, the current progress function (if any) will be unregistered. /// public void RegisterCallback(ProgressCallback callback) { RegisterCallback(callback, null); } /// /// Register a progress function with a . /// /// /// Pointer to the progress function to register. /// If the WIM already has a progress function registered, it will be replaced with this one. /// If null, the current progress function (if any) will be unregistered. /// /// /// The value which will be passed as the third argument to calls to progfunc. /// public void RegisterCallback(ProgressCallback callback, object userData) { if (callback != null) { // RegisterCallback _managedCallback = new ManagedProgressCallback(callback, userData); Lib.RegisterProgressFunction(_ptr, _managedCallback.NativeFunc, IntPtr.Zero); } else { // Delete callback _managedCallback = null; Lib.RegisterProgressFunction(_ptr, null, IntPtr.Zero); } } #endregion #region Rename - RenamePath /// /// Rename the to the in the specified image of the wim. /// /// /// This just builds an appropriate and passes it to . /// /// wimlib did not return . public void RenamePath(int image, string sourcePath, string destPath) { ErrorCode ret = Lib.RenamePath(_ptr, image, sourcePath, destPath); WimLibException.CheckErrorCode(ret); } #endregion #region SetImageInfo - SetImageDescription, SetImageFlags, SetImageName, SetImageProperty, SetWimInfo /// /// Change the description of a WIM image. /// Equivalent to SetImageProperty(image, "DESCRIPTION", description) /// /// The 1-based index of the image for which to set the property. /// /// If not null and not empty, the property is set to this value. /// Otherwise, the property is removed from the XML document. /// /// wimlib did not return . public void SetImageDescription(int image, string description) { ErrorCode ret = Lib.SetImageDescription(_ptr, image, description); WimLibException.CheckErrorCode(ret); } /// /// Change what is stored in the <FLAGS> element in the WIM XML document (usually something like "Core" or "Ultimate"). /// Equivalent to SetImageProperty(image, "FLAGS", flags) /// /// The 1-based index of the image for which to set the property. /// /// If not null and not empty, the property is set to this value. /// Otherwise, the property is removed from the XML document. /// /// wimlib did not return . public void SetImageFlags(int image, string flags) { ErrorCode ret = Lib.SetImageFlags(_ptr, image, flags); WimLibException.CheckErrorCode(ret); } /// /// Change the name of a WIM image. /// Equivalent to SetImageProperty(image, "NAME", name) /// /// The 1-based index of the image for which to set the property. /// /// If not null and not empty, the property is set to this value. /// Otherwise, the property is removed from the XML document. /// /// wimlib did not return . public void SetImageName(int image, string name) { ErrorCode ret = Lib.SetImageName(_ptr, image, name); WimLibException.CheckErrorCode(ret); } /// /// Since wimlib v1.8.3: /// add, modify, or remove a per-image property from the WIM's XML document. /// /// /// This is an alternative to , , /// and which allows manipulating any simple string property. /// /// The 1-based index of the image for which to set the property. /// /// The name of the image property in the same format documented for . /// /// Note: if creating a new element using a bracketed index such as "WINDOWS/LANGUAGES/LANGUAGE[2]", the highest index /// that can be specified is one greater than the number of existing elements with that same name, excluding the index. /// That means that if you are adding a list of new elements, /// they must be added sequentially from the first index (1) to the last index (n). /// /// /// If not null and not empty, the property is set to this value. /// Otherwise, the property is removed from the XML document. /// /// wimlib did not return . public void SetImageProperty(int image, string propertyName, string propertyValue) { ErrorCode ret = Lib.SetImageProperty(_ptr, image, propertyName, propertyValue); WimLibException.CheckErrorCode(ret); } /// /// Set basic information about a WIM. /// /// /// Pointer to a WimInfo structure that contains the information to set. /// Only the information explicitly specified in the which flags need be valid. /// /// /// Flags that specify which information to set. /// This is a bitwise OR of , /// , , and/or . /// /// wimlib did not return . public void SetWimInfo(WimInfo info, ChangeFlags which) { ErrorCode ret = Lib.SetWimInfo(_ptr, info, which); WimLibException.CheckErrorCode(ret); } #endregion #region SetOutput - SetOutputChunkSize, SetOutputPackChunkSize, SetOutputCompressionType, SetOutputPackCompressionType /// /// Set a 's output compression chunk size. /// This is the compression chunk size that will be used for writing non-solid resources /// in subsequent calls to or . /// A larger compression chunk size often results in a better compression ratio, /// but compression may be slower and the speed of random access to data may be reduced. /// In addition, some chunk sizes are not compatible with Microsoft software. /// /// /// The chunk size (in bytes) to set. /// The valid chunk sizes are dependent on the compression type. /// See the documentation for each enum for more information. /// As a special case, if chunkSize is specified as 0, /// then the chunk size will be reset to the default for the currently selected output compression type. /// /// wimlib did not return . public void SetOutputChunkSize(uint chunkSize) { ErrorCode ret = Lib.SetOutputChunkSize(_ptr, chunkSize); WimLibException.CheckErrorCode(ret); } /// /// Similar to , but set the chunk size for writing solid resources. /// /// /// The chunk size (in bytes) to set. /// The valid chunk sizes are dependent on the compression type. /// See the documentation for each enum for more information. /// As a special case, if chunkSize is specified as 0, /// then the chunk size will be reset to the default for the currently selected output compression type. /// /// wimlib did not return . public void SetOutputPackChunkSize(uint chunkSize) { ErrorCode ret = Lib.SetOutputPackChunkSize(_ptr, chunkSize); WimLibException.CheckErrorCode(ret); } /// /// Set a 's output compression type. /// This is the compression type that will be used for writing non-solid resources /// in subsequent calls to or . /// /// /// The compression type to set. /// If this compression type is incompatible with the current output chunk size, /// then the output chunk size will be reset to the default for the new compression type. /// /// wimlib did not return . public void SetOutputCompressionType(CompressionType compType) { ErrorCode ret = Lib.SetOutputCompressionType(_ptr, compType); WimLibException.CheckErrorCode(ret); } /// /// Similar to , but set the compression type for writing solid resources. /// This cannot be . /// /// /// The compression type to set. /// If this compression type is incompatible with the current output chunk size, /// then the output chunk size will be reset to the default for the new compression type. /// /// wimlib did not return . public void SetOutputPackCompressionType(CompressionType compType) { ErrorCode ret = Lib.SetOutputPackCompressionType(_ptr, compType); WimLibException.CheckErrorCode(ret); } #endregion #region Split - Split /// /// Split a WIM into multiple parts. /// /// /// Name of the split WIM (SWM) file to create. /// This will be the name of the first part. /// The other parts will, by default, have the same name with 2, 3, 4, ..., etc. appended before the suffix. /// However, the exact names can be customized using the progress function. /// /// /// The maximum size per part, in bytes. /// Unfortunately, it is not guaranteed that this will really be the maximum size per part, /// because some file resources in the WIM may be larger than this size, /// and the WIM file format provides no way to split up file resources among multiple WIMs. /// /// /// Bitwise OR of WriteFlags. /// These flags will be used to write each split WIM part. /// Specify WriteFlags.DEFAULT here to get the default behavior. /// /// wimlib did not return . public void Split(string swmName, ulong partSize, WriteFlags writeFlags) { ErrorCode ret = Lib.Split(_ptr, swmName, partSize, writeFlags); WimLibException.CheckErrorCode(ret); } #endregion #region Verify - VerifyWim /// /// Perform verification checks on a WIM file. /// /// This function is intended for safety checking and/or debugging. /// If used on a well-formed WIM file, it should always succeed. /// /// /// Note: for an extra layer of verification, it is a good idea /// to have used when you opened the file. /// /// If verifying a split WIM, specify the first part of the split WIM here, /// and reference the other parts using before calling this function. /// /// wimlib did not return . public void VerifyWim() { ErrorCode ret = Lib.VerifyWim(_ptr, 0); WimLibException.CheckErrorCode(ret); } #endregion #region Unmount - (Static) UnmountImage (Linux with FUSE only) /// /// Unmount a WIM image that was mounted using . /// /// /// When unmounting a read-write mounted image, the default behavior is to discard changes to the image. /// Use to cause the image to be committed. /// /// Note: you can also unmount the image by using the umount() system call, or by using the umount or fusermount programs. /// However, you need to call this function if you want changes to be committed. /// /// The directory on which the WIM image is mounted. /// Bitwise OR of . /// wimlib did not return . public static void UnmountImage(string dir, UnmountFlags unmountFlags) { Manager.EnsureLoaded(); ErrorCode ret = Lib.UnmountImage(dir, unmountFlags); WimLibException.CheckErrorCode(ret); } /// /// Same as , but allows specifying a progress function. /// The progress function will receive a message. /// In addition, if changes are committed from a read-write mount, /// the progress function will receive messages. /// /// /// When unmounting a read-write mounted image, the default behavior is to discard changes to the image. /// Use to cause the image to be committed. /// /// Note: you can also unmount the image by using the umount() system call, or by using the umount or fusermount programs. /// However, you need to call this function if you want changes to be committed. /// /// The directory on which the WIM image is mounted. /// Bitwise OR of . /// Callback function to receive progress report. /// Data to be passed to callback function. /// wimlib did not return . public static void UnmountImage(string dir, UnmountFlags unmountFlags, ProgressCallback callback, object userData = null) { Manager.EnsureLoaded(); if (callback == null) throw new ArgumentNullException(nameof(callback)); ManagedProgressCallback mCallback = new ManagedProgressCallback(callback, userData); ErrorCode ret = Lib.UnmountImageWithProgress(dir, unmountFlags, mCallback.NativeFunc, IntPtr.Zero); WimLibException.CheckErrorCode(ret); } #endregion #region Update - UpdateImage /// /// Update a WIM image by adding, deleting, and/or renaming files or directories. /// /// The 1-based index of the image to update. /// that specify the update operations to perform. /// Number of commands in cmd. /// wimlib did not return . public void UpdateImage(int image, UpdateCommand cmd, UpdateFlags updateFlags) { ErrorCode ret; switch (Lib.PlatformBitness) { case PlatformBitness.Bit32: UpdateCommand32[] cmds32 = new UpdateCommand32[1] { cmd.ToNativeStruct32() }; try { ret = Lib.UpdateImage32(_ptr, image, cmds32, 1u, updateFlags); } finally { cmds32[0].Free(); } break; case PlatformBitness.Bit64: UpdateCommand64[] cmds64 = new UpdateCommand64[1] { cmd.ToNativeStruct64() }; try { ret = Lib.UpdateImage64(_ptr, image, cmds64, 1u, updateFlags); } finally { cmds64[0].Free(); } break; default: throw new PlatformNotSupportedException(); } WimLibException.CheckErrorCode(ret); } /// /// Update a WIM image by adding, deleting, and/or renaming files or directories. /// /// /// The 1-based index of the image to update. /// /// /// An array of 's that specify the update operations to perform. /// /// /// Number of commands in cmds. /// /// wimlib did not return . public void UpdateImage(int image, IEnumerable cmds, UpdateFlags updateFlags) { ErrorCode ret; switch (Lib.PlatformBitness) { case PlatformBitness.Bit32: UpdateCommand32[] cmds32 = cmds.Select(x => x.ToNativeStruct32()).ToArray(); try { ret = Lib.UpdateImage32(_ptr, image, cmds32, (uint)cmds32.Length, updateFlags); } finally { foreach (UpdateCommand32 cmd32 in cmds32) cmd32.Free(); } break; case PlatformBitness.Bit64: UpdateCommand64[] cmds64 = cmds.Select(x => x.ToNativeStruct64()).ToArray(); try { ret = Lib.UpdateImage64(_ptr, image, cmds64, (ulong)cmds64.Length, updateFlags); } finally { foreach (UpdateCommand64 cmd64 in cmds64) cmd64.Free(); } break; default: throw new PlatformNotSupportedException(); } WimLibException.CheckErrorCode(ret); } #endregion #region Write - Write, Overwrite /// /// Persist a to a new on-disk WIM file. /// /// /// The path to the on-disk file to write. /// /// /// Normally, specify here. /// This indicates that all images are to be included in the new on-disk WIM file. /// If for some reason you only want to include a single image, specify the 1-based index of that image instead. /// /// /// Bitwise OR of WriteFlags. /// /// /// The number of threads to use for compressing data, or 0 to have the library automatically choose an appropriate number. /// /// /// This brings in file data from any external locations, such as directory trees or NTFS volumes scanned with , /// or other WIM files via , and incorporates it into a new on-disk WIM file. /// /// By default, the new WIM file is written as stand-alone. /// Using the flag, a "delta" WIM can be written instead. /// However, this function cannot directly write a "split" WIM; use for that. /// /// wimlib did not return . public void Write(string path, int image, WriteFlags writeFlags, uint numThreads) { ErrorCode ret = Lib.Write(_ptr, path, image, writeFlags, numThreads); WimLibException.CheckErrorCode(ret); } /// /// Commit a to disk, updating its backing file. /// /// Bitwise OR of relevant . /// /// The number of threads to use for compressing data, /// or Wim.DefaultThreads to have the library automatically choose an appropriate number. /// /// /// There are several alternative ways in which changes may be committed: /// /// 1. Full Rebuild : /// Write the updated WIM to a temporary file, then rename the temporary file to the original. /// 2. Appending : /// Append updates to the new original WIM file, then overwrite its header such that those changes become visible to new readers. /// 3. Compaction : /// Normally should not be used; see for details. /// /// Append mode is often much faster than a full rebuild, but it wastes some amount of space due to leaving "holes" in the WIM file. /// Because of the greater efficiency, normally defaults to append mode. /// However, can be used to explicitly request a full rebuild. /// In addition, if has been used on the , then the default mode switches to rebuild mode, /// and can be used to explicitly request append mode. /// /// If this function completes successfully, then no more functions can be called. /// If you need to continue using the WIM file, you must reopen the file with to open a new for it. /// /// wimlib did not return . public void Overwrite(WriteFlags writeFlags, uint numThreads) { ErrorCode ret = Lib.Overwrite(_ptr, writeFlags, numThreads); WimLibException.CheckErrorCode(ret); } #endregion #region Existence Check (ManagedWimLib specific) /// /// Check if a file exists in wim. /// /// /// The 1-based index of the image that contains wimFilePath, or to iterate over all images. /// /// /// Path of file in the wim image. /// /// Whether a file exists. public bool FileExists(int image, string wimFilePath) { const int ResultFileExists = -1; const int ResultFileDoesNotExist = IterateCallbackSuccess; static int FileExistCallback(DirEntry dentry, object userData) { if ((dentry.Attributes & FileAttributes.Directory) == 0) return ResultFileExists; else return ResultFileDoesNotExist; } int result = IterateDirTree(image, wimFilePath, IterateDirTreeFlags.None, FileExistCallback, null, out ErrorCode ret); return ret switch { ErrorCode.Success => result == ResultFileExists, ErrorCode.PathDoesNotExist => false, _ => throw new WimLibException(ret), }; } /// /// Check if a directory exists in wim file. /// /// /// The 1-based index of the image that contains wimDirPath, or WimLibConst.ALL_IMAGES to iterate over all images. /// /// /// Path of directory in the wim image. /// /// Whether a directory exists. public bool DirExists(int image, string wimDirPath) { const int ResultDirExists = -1; const int ResultDirDoesNotExist = IterateCallbackSuccess; static int DirExistCallback(DirEntry dentry, object userData) { if ((dentry.Attributes & FileAttributes.Directory) != 0) return ResultDirExists; else return ResultDirDoesNotExist; } int result = IterateDirTree(image, wimDirPath, IterateDirTreeFlags.None, DirExistCallback, null, out ErrorCode ret); return ret switch { ErrorCode.Success => result == ResultDirExists, ErrorCode.PathDoesNotExist => false, _ => throw new WimLibException(ret), }; } /// /// Check if a file or a directory exists in wim /// /// /// The 1-based index of the image that contains wimPath, or WimLibConst.ALL_IMAGES to iterate over all images. /// /// /// Path of file or directory in the wim image. /// /// Whether a file or a directory exists. public bool PathExists(int image, string wimPath) { IterateDirTree(image, wimPath, IterateDirTreeFlags.None, null, null, out ErrorCode ret); return ret switch { ErrorCode.Success => true, ErrorCode.PathDoesNotExist => false, _ => throw new WimLibException(ret), }; } #endregion #region CompressInfo - SetDefaultCompressionLevel, GetCompressorNeededMemory /// /// Set the default compression level for the specified compression type. /// This is the compression level that /// assumes if it is called with compLevel specified as 0.
/// wimlib's WIM writing code (e.g. ) will pass 0 /// to internally.
/// Therefore, calling this function will affect the compression level of any data later /// written to WIM files using the specified compression type.
/// /// The initial state, before this function is called, is that all compression types have a default compression level of 50. ///
/// /// Compression type for which to set the default compression level, as one of the . /// /// /// The default compression level to set. If (0), the "default default" level of 50 is restored. /// Otherwise, a higher value indicates higher compression, whereas a lower value indicates lower compression. /// /// /// Flag creates the compressor in a mode where it is allowed to modify the input buffer. /// Specifically, in this mode, if compression succeeds, the input buffer may have been modified, /// whereas if compression does not succeed the input buffer still may have been written to but will have been restored exactly to its original state. /// This mode is designed to save some memory when using large buffer sizes. /// /// wimlib did not return . public static void SetDefaultCompressionLevel(CompressionType ctype, uint compressionLevel, CompressorFlags compressorFlags) { Manager.EnsureLoaded(); compressionLevel |= (uint)compressorFlags; ErrorCode ret = Lib.SetDefaultCompressionLevel((int)ctype, compressionLevel); WimLibException.CheckErrorCode(ret); } /// /// Set the default compression level for the specified compression type. /// This is the compression level that /// assumes if it is called with compLevel specified as 0.
/// wimlib's WIM writing code (e.g. ) will pass 0 /// to internally.
/// Therefore, calling this function will affect the compression level of any data later /// written to WIM files using the specified compression type.
/// /// The initial state, before this function is called, is that all compression types have a default compression level of 50. ///
/// /// The default compression level to set. If (0), the "default default" level of 50 is restored. /// Otherwise, a higher value indicates higher compression, whereas a lower value indicates lower compression. /// /// /// Flag creates the compressor in a mode where it is allowed to modify the input buffer. /// Specifically, in this mode, if compression succeeds, the input buffer may have been modified, /// whereas if compression does not succeed the input buffer still may have been written to but will have been restored exactly to its original state. /// This mode is designed to save some memory when using large buffer sizes. /// /// wimlib did not return . public static void SetEveryDefaultCompressionLevel(uint compressionLevel, CompressorFlags compressorFlags) { Manager.EnsureLoaded(); compressionLevel |= (uint)compressorFlags; ErrorCode ret = Lib.SetDefaultCompressionLevel(EveryCompressionType, compressionLevel); WimLibException.CheckErrorCode(ret); } /// /// Return the approximate number of bytes needed to allocate a compressor with for the specified compression type, maximum block size, and compression level. /// may be (0), in which case the current default compression level for is used. /// /// Returns 0 if the compression type is invalid, or the for that compression type is invalid. /// Used a size greater than uint.MaxValue in 32bit platform. public static ulong GetCompressorNeededMemory(CompressionType ctype, ulong maxBlockSize, uint compressionLevel, CompressorFlags compressorFlags) { Manager.EnsureLoaded(); compressionLevel |= (uint)compressorFlags; UIntPtr maxBlockSizeInterop = new UIntPtr(maxBlockSize); return Lib.GetCompressorNeededMemory(ctype, maxBlockSizeInterop, compressionLevel); } #endregion } } ================================================ FILE: README.md ================================================ # AME Wizard Core Core functionality used by AME Wizard. ## CLI Usage *We do not recommend CLI usage for normal users, instead use [AME Wizard](https://ameliorated.io/).* 1. Download `CLI-Standalone.zip` from the [latest release](https://github.com/Ameliorated-LLC/trusted-uninstaller-cli/releases/latest) 2. Extract the downloaded archive 3. Inside the extracted folder, place a Playbook of choice 4. Extract the Playbook with 7zip using the password `malte` 5. Open **Command Prompt** as administrator and navigate to the extracted CLI-Standalone folder 6. Run `TrustedUninstaller.CLI.exe ""` Optionally, you can specify options like in the following example: ``` TrustedUninstaller.CLI.exe "AME 11 v0.7" browser-firefox enhanced-security ``` ## Compilation 1. Clone the repository ``` git clone https://github.com/Ameliorated-LLC/trusted-uninstaller-cli.git ``` 2. Open TrustedUninstaller.sln with Visual Studio or JetBrains Rider 3. Set the configuration to **Release** 4. Build TrustedUninstaller.CLI ## 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. Since this project makes major alterations to the operating system and has the ability to install software during this process, it is imperative that we **provide its source code for auditing purposes.** This has not only helped us build trust, and make our project stand out among the crowd, but has also led to many community contributions along the way. ================================================ FILE: TrustedUninstaller.CLI/CLI.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Mime; using System.Reflection; using System.Runtime.Serialization; using System.Security; using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using Core; using Interprocess; using Microsoft.Win32; using TrustedUninstaller.Shared; using TrustedUninstaller.Shared.Actions; using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.CLI { public class CLI { private static async Task ParseArguments(string[] args) { CommandLine.IArgumentData argumentsData = null; try { argumentsData = CommandLine.ParseArguments(args); } catch (Exception exception) { Console.WriteLine("Command line error: " + exception.Message); Environment.Exit(1); } if (argumentsData is CommandLine.Interprocess interprocessData) { if (interprocessData.Level != Level.Disposable && !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) throw new SecurityException("Process must be run as an administrator."); Directory.SetCurrentDirectory(Path.GetDirectoryName(Win32.ProcessEx.GetCurrentProcessFileLocation())!); await InterLink.InitializeConnection(interprocessData.Level, interprocessData.Mode, interprocessData.Host, interprocessData.Nodes?.Select(x => (Level: x.Level, ProcessID: x.ProcessID)).ToArray() ?? null); Environment.Exit(376); } } private static async System.Threading.Tasks.Task Main(string[] args) { if (args.Length > 1 && args[1] == "Interprocess") await ParseArguments(args.Skip(1).ToArray()); //Needed after defender removal's reboot, the "current directory" will be set to System32 //After the auto start up. Directory.SetCurrentDirectory(Path.GetDirectoryName(Win32.ProcessEx.GetCurrentProcessFileLocation())!); if (!WinUtil.IsAdministrator()) { System.Console.Error.WriteLine("This program must be launched as an Administrator!"); return -1; } #if !DEBUG /* if (!WinUtil.IsGenuineWindows()) { System.Console.Error.WriteLine("This program only works on genuine Windows copies!"); return -1; } */ #endif if (args.Length < 1 || !Directory.Exists(args[0])) { Console.WriteLine("No Playbook selected."); return -1; } AmeliorationUtil.Playbook = AmeliorationUtil.DeserializePlaybook(Path.GetFullPath(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."); Console.WriteLine($"Current directory: {Directory.GetCurrentDirectory()}"); return -1; } ExtractResourceFolder("resources", Directory.GetCurrentDirectory()); await InterLink.InitializeConnection(Level.Administrator, Mode.TwoWay); if (!WinUtil.IsTrustedInstaller()) { Console.WriteLine("Checking requirements...\r\n"); if (AmeliorationUtil.Playbook.Requirements.Contains(Requirements.Requirement.Internet) && !await (new Requirements.Internet()).IsMet()) { Console.WriteLine("Internet must be connected to run this Playbook."); } bool ucpdDisablePending = AmeliorationUtil.Playbook.Requirements.Contains(Requirements.Requirement.UCPDDisabled) && !await new Requirements.UCPDDisabled().IsMet(); if (((AmeliorationUtil.Playbook.Requirements.Contains(Requirements.Requirement.DefenderDisabled) || AmeliorationUtil.Playbook.Requirements.Contains(Requirements.Requirement.DefenderToggled)) && Process.GetProcessesByName("MsMpEng").Any()) || ucpdDisablePending) { bool first = true; while ((await GetDefenderToggles()).Any(x => x)) { 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...\r\n"); Console.ReadKey(); } if ((AmeliorationUtil.Playbook.Requirements.Contains(Requirements.Requirement.DefenderDisabled) && Process.GetProcessesByName("MsMpEng").Any()) || ucpdDisablePending) { bool remnantsOnly = false; 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 { Console.WriteLine("\r\nPreparing system..."); await PrepareSystemCLI(false, ucpdDisablePending, AmeliorationUtil.Playbook.Requirements.Contains(Requirements.Requirement.DefenderDisabled) && Process.GetProcessesByName("MsMpEng").Any()); Console.WriteLine("Preparation Complete"); if (!remnantsOnly) { Console.WriteLine("\r\nRestarting system..."); CmdAction reboot = new CmdAction() { Command = "timeout /t 1 & shutdown /r /t 0", Wait = false }; Wrap.ExecuteSafe(() => reboot.RunTaskOnMainThread(Output.OutputWriter.Null)); Environment.Exit(0); } } catch (Exception e) { Console.WriteLine("Error preparing system: " + e.Message); Environment.Exit(-1); } } } if (AmeliorationUtil.Playbook.Requirements.Contains(Requirements.Requirement.Internet) && !await (new Requirements.Internet()).IsMet()) { 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; } if (!AmeliorationUtil.Playbook.UseKernelDriver.HasValue) { if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 1, }.GetStatus(Output.OutputWriter.Null) != UninstallTaskStatus.Completed && new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.GetStatus(Output.OutputWriter.Null) == UninstallTaskStatus.Completed && (await GetDefenderToggles()).All(toggleOn => !toggleOn)) { AmeliorationUtil.UseKernelDriver = true; } } else AmeliorationUtil.UseKernelDriver = AmeliorationUtil.Playbook.UseKernelDriver.Value; try { if (!Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ame-assassin"))) { Console.WriteLine("Extracting resources"); ExtractResourceFolder("resources", Directory.GetCurrentDirectory()); ExtractArchive(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CLI-Resources.7z"), AppDomain.CurrentDomain.BaseDirectory); if (AmeliorationUtil.UseKernelDriver) ExtractArchive(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProcessInformer.7z"), AppDomain.CurrentDomain.BaseDirectory); try { File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CLI-Resources.7z")); File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProcessInformer.7z")); } catch (Exception e) { } } } catch (Exception e) { Console.WriteLine($"Error extracting resources."); return -1; } var launchResult = await SafeTask.Run( () => InterLink.LaunchNode(TargetLevel.Administrator, arguments => NativeProcess.StartProcessAsTI(Win32.ProcessEx.GetCurrentProcessFileLocation(), arguments), Level.TrustedInstaller, Mode.TwoWay, Process.GetCurrentProcess().Id, false), true); if (launchResult.Failed) { Console.WriteLine("Could not initialize process. Check the error logs and contact the team " + "for more information and assistance."); Environment.Exit(1); } List options = null; if (args.Length > 1) { options = args.Skip(1).ToList(); } var allOptions = AmeliorationUtil.Playbook.FeaturePages == null ? new string[] { } : AmeliorationUtil.Playbook.FeaturePages.SelectMany(x => x.Options.Select(o => o.Name)).Where(x => !string.IsNullOrEmpty(x)).ToArray(); var status = "Starting Playbook"; bool errorsOccurred = false; try { using (var reporter = new InterLink.InterMessageReporter(statusText => { status = statusText.TrimEnd('.') + "..."; })) { using (var progress = new InterLink.InterProgress(async value => { Console.WriteLine(value + "% " + status + "..."); })) { errorsOccurred = await InterLink.ExecuteAsync(() => AmeliorationUtil.RunPlaybook(AmeliorationUtil.Playbook.Path, true, false, null, null, null, AmeliorationUtil.Playbook.Name, AmeliorationUtil.Playbook.Version, options.ToArray(), allOptions, Environment.CurrentDirectory, progress, reporter, AmeliorationUtil.UseKernelDriver)); } } } catch (Exception exception) { InterLink.ShutdownNode(Level.TrustedInstaller); if (exception is SerializableException serializableException && serializableException.OriginalType.Type == typeof(SerializationException)) { Console.WriteLine("\r\nYAML Error: " + exception.ToString()); Environment.Exit(1); } Console.WriteLine("\r\nFatal Playbook Error: " + exception.ToString()); Environment.Exit(1); } if (errorsOccurred) Console.WriteLine("\r\nPlaybook completed with errors."); else Console.WriteLine("\r\nPlaybook completed successfully."); return 0; } public static void ExtractArchive(string file, string targetDir) { RunCommand($"x \"{file}\" -o\"{targetDir}\" -p\"wizard\" -y -aos"); } private static void RunCommand(string command) { var proc = new Process(); var startInfo = new ProcessStartInfo { CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, Arguments = command, FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "7za.exe"), RedirectStandardError = true, }; proc.StartInfo = startInfo; proc.Start(); StringBuilder errorOutput = new StringBuilder(""); proc.ErrorDataReceived += (sender, args) => { errorOutput.Append("\r\n" + args.Data); }; proc.BeginErrorReadLine(); proc.WaitForExit(); proc.CancelErrorRead(); if (proc.ExitCode == 1) Log.EnqueueSafe(LogType.Error, "Warning while running 7zip: " + errorOutput.ToString(), null, ("Command", command)); if (proc.ExitCode > 1) throw new ArgumentOutOfRangeException("Error running 7zip: " + errorOutput.ToString()); } public static void ExtractResourceFolder(string resource, string dir, bool overwrite = false) { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); Assembly assembly = Assembly.GetExecutingAssembly(); var resources = assembly.GetManifestResourceNames().Where(res => res.StartsWith($"TrustedUninstaller.CLI.Properties.{resource}.")); foreach (var obj in resources) { using (UnmanagedMemoryStream stream = (UnmanagedMemoryStream)assembly.GetManifestResourceStream(obj)) { int MB = 1024 * 1024; int offset = -MB; var file = dir + $"\\{obj.Substring($"TrustedUninstaller.CLI.Properties.{resource}.".Length).Replace("---", "\\")}"; if (file.EndsWith(".gitkeep")) continue; var fileDir = Path.GetDirectoryName(file); if (fileDir != null && !Directory.Exists(fileDir)) Directory.CreateDirectory(fileDir); if (File.Exists(file) && !overwrite) continue; if (File.Exists(file) && overwrite) { try { File.Delete(file); } catch (Exception e) { if (!Directory.Exists(Directory.GetCurrentDirectory() + "\\Logs")) Directory.CreateDirectory(Directory.GetCurrentDirectory() + "\\Logs"); using (var writer = new StreamWriter(Path.Combine(Directory.GetCurrentDirectory(), "Logs\\ErrorLog.txt"), true)) { writer.WriteLine($"Title: Could not delete existing resource file {file}.\r\nMessage: {e.Message}\r\n\r\nStackTrace: {e.StackTrace}"); writer.WriteLine("\r\nDate/Time: " + DateTime.Now); writer.WriteLine("============================================"); } continue; } } using (FileStream fsDlst = new FileStream(file, FileMode.CreateNew, FileAccess.Write)) { while (offset + MB < stream.Length) { var buffer = new byte[MB]; offset += MB; if (offset + MB > stream.Length) { var bytesLeft = stream.Length - offset; buffer = new byte[bytesLeft]; } stream.Seek(offset, SeekOrigin.Begin); stream.Read(buffer, 0, buffer.Length); fsDlst.Seek(offset, SeekOrigin.Begin); fsDlst.Write(buffer, 0, buffer.Length); } } } } } public static async Task> GetDefenderToggles() { var result = new List(); await Task.Run(() => { var defenderKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows Defender"); var policiesKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Policies\Microsoft\Windows Defender"); RegistryKey realtimePolicy = null; RegistryKey realtimeKey = null; try { try { realtimePolicy = policiesKey.OpenSubKey("Real-Time Protection"); } catch (Exception e) { } if (realtimePolicy != null) realtimeKey = realtimePolicy; else realtimeKey = defenderKey.OpenSubKey("Real-Time Protection"); } catch { result.Add(false); } if (realtimeKey != null) { try { result.Add((int)realtimeKey.GetValue("DisableRealtimeMonitoring") != 1); } catch (Exception exception) { try { realtimeKey = defenderKey.OpenSubKey("Real-Time Protection"); result.Add((int)realtimeKey.GetValue("DisableRealtimeMonitoring") != 1); } catch (Exception e) { result.Add(true); } } } try { RegistryKey spynetPolicy = null; RegistryKey spynetKey = null; try { spynetPolicy = policiesKey.OpenSubKey("SpyNet"); } catch (Exception e) { } if (spynetPolicy != null) spynetKey = spynetPolicy; else spynetKey = defenderKey.OpenSubKey("SpyNet"); int reporting = 0; int consent = 0; try { reporting = (int)spynetKey.GetValue("SpyNetReporting"); } catch (Exception e) { if (spynetPolicy != null) { reporting = (int)defenderKey.OpenSubKey("SpyNet").GetValue("SpyNetReporting"); } } try { consent = (int)spynetKey.GetValue("SubmitSamplesConsent"); } catch (Exception e) { if (spynetPolicy != null) { consent = (int)defenderKey.OpenSubKey("SpyNet").GetValue("SubmitSamplesConsent"); } } result.Add(reporting != 0); result.Add(consent != 0 && consent != 2 && consent != 4); } catch { result.Add(false); result.Add(false); } try { int tamper = (int)defenderKey.OpenSubKey("Features").GetValue("TamperProtection"); result.Add(tamper != 4 && tamper != 0); } catch { result.Add(false); } }); return result; } public static async Task PrepareSystemCLI(bool KernelDriverOnly, bool ucpdDisablePending, bool defenderDisablePending) { var status = "Adding certificate"; using var progress = new InterLink.InterProgress(value => Console.WriteLine(value + "% " + status + "...")); using var messageReporter = new InterLink.InterMessageReporter(message => status = message); var workTask = KernelDriverOnly ? InterLink.ExecuteAsync(() => Defender.DisableBlocklist(progress, messageReporter, ucpdDisablePending)) : !defenderDisablePending ? InterLink.ExecuteAsync(() => Defender.DisableUCPD(progress)) : InterLink.ExecuteAsync(() => Defender.KillAndDisable(progress, messageReporter, false, true)); await workTask; } } } ================================================ FILE: TrustedUninstaller.CLI/CommandLine.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.Internal; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security; using System.Security.Principal; using System.Windows; using Core; using Interprocess; using JetBrains.Annotations; using Exception = System.Exception; namespace TrustedUninstaller.CLI { public static class CommandLine { #region Deserializers public class Interprocess : IArgumentData { [Required] [DefaultArgument] public Level Level { get; set; } [Required] public Mode Mode { get; set; } [CanBeNull] public NodeData[] Nodes { get; set; } = null; public int Host { get; set; } = -1; public class NodeData { public InterLink.InternalLevel Level { get; set; } public int ProcessID { get; set; } } } public class Execute : IArgumentData { [Required] [DefaultArgument] public CommandType Command { get; set; } public enum CommandType { [RequiresArgumentData("RunData")] Run, Delete, } [RequiredArgumentData] public Run RunData { get; set; } public class Run : IArgumentData { [Required] [DefaultArgument] public string File { get; set; } } } #endregion #region Deserialization public static string SerializeArgument([NotNull] object value) { Type propertyType = value.GetType(); if (propertyType.IsValueTuple()) { var properties = propertyType.GetFields(); var serializedProperties = new List(); foreach (var property in properties) { var propertyValue = property.GetValue(value); serializedProperties.Add($"{property.Name}={propertyValue?.ToString().Replace(":", "::")}"); } return string.Join(":", serializedProperties); } else { throw new ArgumentException("Expected a ValueTuple", nameof(value)); } } [CanBeNull] public static IArgumentData ParseArguments() => ParseArguments(Environment.GetCommandLineArgs().Skip(1).ToArray()); [CanBeNull] public static IArgumentData ParseArguments(string[] args) { if (args.Length == 0) return null; var dataClasses = typeof(CommandLine).GetNestedTypes().Where(x => x.GetInterfaces().Contains(typeof(IArgumentData))).ToArray(); var dataClass = dataClasses.FirstOrDefault(x => x.Name.Equals($"{args[0]}")); if (dataClass == null) throw new SerializationException("First argument must be one of the following: \r\n" + String.Join(Environment.NewLine, dataClasses.Select(x => x.Name))); var argsToParse = args.Skip(1).ToList(); var data = (IArgumentData)Activator.CreateInstance(dataClass); return DeserializeArguments(argsToParse, data); } private static IArgumentData DeserializeArguments(List args, IArgumentData result) { var properties = result.GetType().GetProperties(); PropertyInfo defaultProperty = null; if (args.Count > 0 && !args[0].StartsWith("--")) { defaultProperty = properties.FirstOrDefault(x => x.GetCustomAttribute(typeof(DefaultArgumentAttribute)) != null); if (defaultProperty == null) throw new SerializationException($"Unexpected argument '{args[0]}'."); } List propertiesParsed = new List(); bool passedArgument = true; for (int i = 0; i < args.Count; i++) { if (!args[i].StartsWith("--")) { if (i == 0 && defaultProperty != null) i--; else { if (passedArgument) throw new SerializationException($"Expected an argument starting with '--', instead got '{args[i]}'. Make sure to quote any arguments that contain spaces or special characters."); passedArgument = true; continue; } } passedArgument = false; var property = defaultProperty ?? properties.FirstOrDefault(x => x.Name.Equals(args[i].Substring(2), StringComparison.OrdinalIgnoreCase) && x.GetCustomAttribute(typeof(RequiredArgumentDataAttribute)) == null); if (property == null) throw new SerializationException($"Unrecognized argument '{args[i]}'."); if (propertiesParsed.Contains(property.Name)) throw new SerializationException($"Duplicate argument '{args[i]}'."); defaultProperty = null; propertiesParsed.Add(property.Name); if (property.PropertyType == typeof(bool) && (args.Count - 1 == i || args[i + 1].StartsWith("--"))) { DeserializeArgument(property.PropertyType, "true", result); continue; } if (args.Count - 1 == i) throw new SerializationException($"An empty value is not valid for '--{property.Name}'."); if (property.PropertyType.IsEnum) { var converter = TypeDescriptor.GetConverter(property.PropertyType); var enumValue = Wrap.ExecuteSafe(() => converter.ConvertFromString(args[i + 1])).Value; if (enumValue == null) throw new SerializationException($"Argument '{args[i + 1]}' must be one of the following: \r\n" + String.Join(Environment.NewLine, Enum.GetNames(property.PropertyType))); property.SetValue(result, enumValue); if (!EnumValueHasAttribute(property.PropertyType, enumValue, typeof(RequiresArgumentDataAttribute))) continue; var attribute = (RequiresArgumentDataAttribute)Attribute.GetCustomAttribute( property.PropertyType.GetField(enumValue.ToString()), typeof(RequiresArgumentDataAttribute)); var requiredDataProperty = properties.FirstOrDefault(x => x.Name == attribute.RequiredProperty); if (requiredDataProperty == null) throw new SerializationException($"Required property '{attribute.RequiredProperty}' not found in class '{result.GetType().Name}'."); if (!typeof(IArgumentData).IsAssignableFrom(requiredDataProperty.PropertyType)) throw new SerializationException($"Required property '{attribute.RequiredProperty}' type does not implement the 'IArgumentData' interface."); var requiredData = (IArgumentData)Activator.CreateInstance(requiredDataProperty.PropertyType); DeserializeArguments(args.Skip(args.FindIndex(x => ReferenceEquals(x, args[i + 1])) + 1).ToList(), requiredData); requiredDataProperty.SetValue(result, requiredData); break; } else property.SetValue(result, DeserializeArgument(property.PropertyType, args[i + 1], result)); } var requiredProperty = properties.FirstOrDefault(x => x.GetCustomAttribute(typeof(RequiredAttribute)) != null && !propertiesParsed.Contains(x.Name)); if (requiredProperty != null) throw new SerializationException($"Missing required argument '--{requiredProperty.Name}'"); return result; } [NotNull] private static object DeserializeArgument(Type propertyType, string value, IArgumentData data) { if (propertyType.IsArray) { var itemType = propertyType.GetElementType(); if (itemType!.IsArray) throw new SerializationException("Arrays of arrays are not supported."); var itemValues = value.Split(','); List items = new List(); foreach (var itemValue in itemValues) { items.Add(DeserializeArgument(itemType, itemValue, data)); } Array itemsOfType = Array.CreateInstance(itemType!, items.Count); for (int i = 0; i < items.Count; i++) { itemsOfType.SetValue(Convert.ChangeType(items[i], itemType), i); } return itemsOfType; } if (propertyType == typeof(string)) { return value; } else if (propertyType == typeof(bool)) { if (!bool.TryParse(value, out bool boolValue)) throw new SerializationException($"Expected 'true' or 'false' for '--{propertyType.Name}'."); return boolValue; } else if (propertyType == typeof(int)) { if (!int.TryParse(value, out int intValue)) throw new SerializationException($"Expected a number for '--{propertyType.Name}'."); return intValue; } else if (propertyType == typeof(long)) { if (!long.TryParse(value, out long longValue)) throw new SerializationException($"Expected a number for '--{propertyType.Name}'."); return longValue; } else if (propertyType.IsClass) { var instance = Activator.CreateInstance(propertyType); foreach (var item in value.Split(':')) { var parts = item.Split('='); var propName = parts[0].Trim(); var propValue = (parts.Length > 1) ? parts[1].Trim().Replace("::", ":") : null; if (string.IsNullOrWhiteSpace(propValue)) continue; var property = propertyType.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance); if (property != null) { object convertedValue = null; if (property.PropertyType.IsEnum) convertedValue = Enum.Parse(property.PropertyType, propValue); else convertedValue = Convert.ChangeType(propValue, property.PropertyType); property.SetValue(instance, convertedValue); } } return instance; } throw new SerializationException($"Unexpected type '{propertyType.Name}' of property in class '{data.GetType().Name}'."); } public static bool EnumValueHasAttribute(Type enumType, object enumValue, Type attributeType) { var fieldInfo = enumType.GetField(enumValue.ToString()); var attribute = (Attribute)Attribute.GetCustomAttribute(fieldInfo, attributeType); return attribute != null; } #endregion #region Definitions public interface IArgumentData { } [AttributeUsage(AttributeTargets.Field)] public class RequiresArgumentDataAttribute : Attribute { public string RequiredProperty { get; set; } public RequiresArgumentDataAttribute(string requiredProperty) => RequiredProperty = requiredProperty; } public class RequiredArgumentDataAttribute : Attribute { } public class DefaultArgumentAttribute : Attribute { } public class ArgumentDictionary { private Dictionary _dictionary = new Dictionary(); private List _index = new List(); public void Add(TKey key, TValue value) { _dictionary.Add(key, value); _index.Add(key); } public bool TryGetValueAfterIndex(int index, TKey key, out TValue value) { bool found = _dictionary.TryGetValue(key, out value); if (!found) return false; int indexPosition = _index.IndexOf(key); if (indexPosition > index) { return true; } throw new SerializationException($"Argument '--{key}' must come after '--{_index[index]}'."); } public int GetIndex(TKey key) => _index.IndexOf(key); public TValue this[TKey key] { get { return _dictionary[key]; } private set => _dictionary[key] = value; } } #endregion } } ================================================ FILE: TrustedUninstaller.CLI/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using TrustedUninstaller.Shared; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("TrustedUninstaller.CLI")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Ameliorated LLC")] [assembly: AssemblyProduct("TrustedUninstaller.CLI")] [assembly: AssemblyCopyright("MIT License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("476d3799-2cfc-4670-94e5-9af51b234b07")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion(Globals.CurrentVersion)] [assembly: AssemblyFileVersion(Globals.CurrentVersion)] ================================================ FILE: TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj ================================================  Debug AnyCPU {476D3799-2CFC-4670-94E5-9AF51B234B07} Exe TrustedUninstaller.CLI TrustedUninstaller.CLI v4.7.2 512 true true true false C:\Users\yohas\Documents\AME\ true Disk false Foreground 7 Days false false true 0 1.0.0.%2a false true true *.xml disable $([System.IO.Path]::GetFullPath('$(SolutionDir)'))=./ true bin\x64\Debug\ DEBUG;TRACE embedded x64 8 prompt MinimumRecommendedRules.ruleset true en bin\x64\Release\ TRACE true embedded x64 8 prompt MinimumRecommendedRules.ruleset true en bin\x64\Release\ x64 true embedded SINGLE bin\x64\Release\ x64 true embedded SINGLE;DEBUG B8F0A67800B779C5CEF49BEAB6E5247E373F9452 TrustedUninstaller.CLI_TemporaryKey.pfx false false LocalIntranet 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") PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'CLI','""""x64\$(Configuration)""""' -NoNewWindow -Wait" .allowedextension 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") ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll False False False Microsoft .NET Framework 4.8 %28x86 and x64%29 true False .NET Framework 3.5 SP1 false {9bda9d32-e9a1-4db8-9d90-443792107e28} TrustedUninstaller.Shared ================================================ FILE: TrustedUninstaller.CLI/app.manifest ================================================  ================================================ FILE: TrustedUninstaller.Shared/Actions/AppxAction.cs ================================================ using System; using System.Collections.Generic; 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; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Xml; using Core; namespace TrustedUninstaller.Shared.Actions { // Integrate ame-assassin later internal class AppxAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } public enum AppxOperation { Remove = 0, ClearCache = 1, } public enum Level { Family = 0, Package = 1, App = 2 } [YamlMember(typeof(string), Alias = "name")] public string Name { get; set; } [YamlMember(typeof(Level), Alias = "type")] public Level? Type { get; set; } = Level.Family; [YamlMember(typeof(AppxOperation), Alias = "operation")] public AppxOperation Operation { get; set; } = AppxOperation.Remove; [YamlMember(typeof(bool), Alias = "verboseOutput")] public bool Verbose { get; set; } = false; [YamlMember(typeof(bool), Alias = "unregister")] public bool Unregister { get; set; } = false; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 30; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"AppxAction failed to remove '{Name}'."; /* private Package GetPackage() { var packageManager = new PackageManager(); return packageManager.FindPackages().FirstOrDefault(package => package.Id.Name == Name); } */ public override string? IsISOCompatible() => "AppxAction does not support iso yet."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) return UninstallTaskStatus.InProgress; return HasFinished ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; //return GetPackage() == null ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } private bool HasFinished = false; public async Task RunTask(Output.OutputWriter output) { if (InProgress) throw new TaskInProgressException("Another Appx action was called while one was in progress."); InProgress = true; output.WriteLineSafe("Info", $"Removing APPX {Type.ToString().ToLower()} '{Name}'..."); if (AmeliorationUtil.ISO) { if (Type == Level.App) { foreach (string manifest in Directory.GetFiles(Path.Combine(AmeliorationUtil.WimPath, "Program Files\\WindowsApps"), "AppxManifest.xml", SearchOption.AllDirectories)) { var xml = new XmlDocument(); xml.Load(manifest); var appName = Name.Trim('*'); var appData = xml.SelectSingleNode($"//*[@Id='{appName}']"); try { if (appData != null) { output.WriteLineSafe("Info", $"\r\nRemoving application xml with Id {appName} from file {manifest}..."); appData.ParentNode!.RemoveChild(appData); } } catch (Exception e) { Console.WriteLine($"\r\nError: Could not remove {appName} from xml document {manifest}.\r\nException: {e.Message}"); } xml.Save(manifest); } foreach (string manifest in Directory.GetFiles(Path.Combine(AmeliorationUtil.WimPath, "Windows\\SystemApps"), "AppxManifest.xml", SearchOption.AllDirectories)) { var xml = new XmlDocument(); xml.Load(manifest); var appName = Name.Trim('*'); var appData = xml.SelectSingleNode($"//*[@Id='{appName}']"); try { if (appData != null) { output.WriteLineSafe("Info", $"\r\nRemoving application xml with Id {appName} from file {manifest}..."); appData.ParentNode!.RemoveChild(appData); } } catch (Exception e) { Log.WriteExceptionSafe(e); } xml.Save(manifest); } } else { foreach (string directory in Directory.GetDirectories(Path.Combine(AmeliorationUtil.WimPath, "Program Files\\WindowsApps"), Name)) { output.WriteLineSafe("Info", $"\r\nRemoving APPX package folder {directory}..."); try { DirectoryInfo dir = new DirectoryInfo(directory); foreach (FileInfo file in dir.GetFiles("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => file.Attributes = FileAttributes.Normal); foreach (DirectoryInfo subDir in dir.GetDirectories("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => subDir.Attributes = FileAttributes.Normal); Wrap.ExecuteSafe(() => dir.Attributes = FileAttributes.Normal); Directory.Delete(directory, true); } catch (Exception e) { var action = new FileAction() { RawPath = directory }; await action.RunTask(output); } } foreach (string directory in Directory.GetDirectories(Path.Combine(AmeliorationUtil.WimPath, "Windows\\SystemApps"), Name)) { output.WriteLineSafe("Info", $"\r\nRemoving APPX package folder {directory}..."); try { DirectoryInfo dir = new DirectoryInfo(directory); foreach (FileInfo file in dir.GetFiles("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => file.Attributes = FileAttributes.Normal); foreach (DirectoryInfo subDir in dir.GetDirectories("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => subDir.Attributes = FileAttributes.Normal); Wrap.ExecuteSafe(() => dir.Attributes = FileAttributes.Normal); Directory.Delete(directory, true); } catch (Exception e) { var action = new FileAction() { RawPath = directory }; await action.RunTask(output); } } } HasFinished = true; InProgress = false; return true; } WinUtil.CheckKph(); string verboseArg = Verbose ? " -Verbose" : ""; string unregisterArg = Unregister ? " -Verbose" : ""; string kernelDriverArg = AmeliorationUtil.UseKernelDriver ? " -UseKernelDriver" : ""; var psi = new ProcessStartInfo() { UseShellExecute = false, CreateNoWindow = true, Arguments = $@"-{Type.ToString()} ""{Name}""" + verboseArg + unregisterArg + kernelDriverArg, FileName = Directory.GetCurrentDirectory() + "\\ame-assassin\\ame-assassin.exe", RedirectStandardOutput = true, RedirectStandardError = true }; if (Operation == AppxOperation.ClearCache) { psi.Arguments = $@"-ClearCache ""{Name}"""; } this.outputWriter = output; Process proc; if (AmeliorationUtil.ISO) { using (var environment = new ProcessEnvironment( ("SYSTEMROOT", Path.Combine(AmeliorationUtil.WimPath, "Windows")), ("WINDIR", Path.Combine(AmeliorationUtil.WimPath, "Windows")), ("SYSTEMDRIVE", AmeliorationUtil.WimPath), ("HOMEDRIVE", AmeliorationUtil.WimPath), ("PROGRAMDATA", Path.Combine(AmeliorationUtil.WimPath, "ProgramData")) )) { proc = Process.Start(psi); } } else proc = Process.Start(psi); proc.OutputDataReceived += ProcOutputHandler; proc.ErrorDataReceived += ProcOutputHandler; proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); bool exited = proc.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning("ame-assassin", proc.Id)) { exited = proc.WaitForExit(30000); } HasFinished = true; InProgress = false; return true; } private Output.OutputWriter outputWriter = null; private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { if (!string.IsNullOrWhiteSpace(outLine.Data)) outputWriter.WriteLineSafe("Process", outLine.Data); } private static bool ExeRunning(string name, int id) { try { return Process.GetProcessesByName(name).Any(x => x.Id == id); } catch (Exception) { return false; } } private static void RemoveISOAppx(Output.OutputWriter output) { } private class ProcessEnvironment : IDisposable { private List<(string Variable, string Value)> oldVariables; public ProcessEnvironment(params (string Variable, string Value)[] variables) { oldVariables = new List<(string Variable, string Value)>(); foreach (var pair in variables) { oldVariables.Add((pair.Variable, Environment.GetEnvironmentVariable(pair.Variable))); Environment.SetEnvironmentVariable(pair.Variable, pair.Value, EnvironmentVariableTarget.Process); } } public void Dispose() { foreach (var oldVariable in oldVariables) { Environment.SetEnvironmentVariable(oldVariable.Variable, oldVariable.Value, EnvironmentVariableTarget.Process); } } } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/CmdAction.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Core; using JetBrains.Annotations; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class CmdAction : Tasks.TaskActionWithOutputProcessor, ITaskAction { public override string? IsISOCompatible() => "For safety reasons, CmdAction does not support iso."; public void RunTaskOnMainThread(Output.OutputWriter output) { if (InProgress) throw new TaskInProgressException("Another Cmd action was called while one was in progress."); InProgress = true; var privilegeText = RunAs == Privilege.CurrentUser ? " as the current user" : RunAs == Privilege.CurrentUserElevated ? " as the current user elevated" : RunAs == Privilege.System ? " as the system account" : ""; output.WriteLineSafe("Info", $"Running cmd command '{Command}'{privilegeText}..."); ExitCode = null; if (RunAs == Privilege.TrustedInstaller) RunAsProcess(output); else RunAsPrivilegedProcess(output); InProgress = false; } [YamlMember(typeof(Privilege), Alias = "runas")] public Privilege RunAs { get; set; } = Privilege.TrustedInstaller; [YamlMember(typeof(string), Alias = "command")] public string Command { get; set; } [YamlMember(typeof(int), Alias = "timeout")] public int? Timeout { get; set; } [YamlMember(typeof(string), Alias = "wait")] public bool Wait { get; set; } = true; [YamlMember(typeof(bool), Alias = "exeDir")] public bool ExeDir { get; set; } = false; [YamlMember(typeof(Dictionary), Alias = "handleExitCodes")] [CanBeNull] public Dictionary HandleExitCodes { get; set; } = null; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; private int? ExitCode { get; set; } public string ErrorString() => $"CmdAction failed to run command '{Command}'."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } return ExitCode == null ? UninstallTaskStatus.ToDo: UninstallTaskStatus.Completed; } public Task RunTask(Output.OutputWriter output) { return null; } private void RunAsProcess(Output.OutputWriter output) { using var process = new Process(); var startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Normal, FileName = "cmd.exe", Arguments = "/C " + $"\"{this.Command}\"", UseShellExecute = false, // .NET has a bug when the start command is used. Using WaitForExit() waits for the // started process to exit, instead of just the original cmd.exe process. For some // reason, using WaitForExit(timeout) does not have this same behavior, and returns // after start finishes as expected. However, the output streams do not output null // either until the started process exits, which is why an exception is made here. // Same for .NET 8.0. RedirectStandardError = !this.Command.StartsWith("start ", StringComparison.OrdinalIgnoreCase), RedirectStandardOutput = !this.Command.StartsWith("start ", StringComparison.OrdinalIgnoreCase), CreateNoWindow = true }; if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } process.StartInfo = startInfo; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(RunAs); if (!Wait) { return; } if (Timeout.HasValue) { var exited = process.WaitForExit(Timeout.Value); if (!exited) { handler.CancelReading(); process.Kill(); throw new TimeoutException($"Command '{Command}' timeout exceeded."); } } else { bool exited = process.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && CmdRunning(process.Id)) { exited = process.WaitForExit(30000); } } } ExitCode = Wrap.ExecuteSafe(() => process.ExitCode, true, output.LogOptions).Value; if (ExitCode != 0 && !Command.Contains("ProcessHacker\\x64\\ProcessHacker.exe")) output.WriteLineSafe("Info", $"cmd instance exited with non-zero exit code: {ExitCode}"); if (HandleExitCodes != null) { foreach (string key in HandleExitCodes.Keys) { if (IsApplicableNumber(key, ExitCode.Value)) { throw new ErrorHandlingException(HandleExitCodes[key], $"Command '{Command}' exit code {ExitCode.Value} handled with filter '{key}' --> {HandleExitCodes[key]}."); } } } } private static bool CmdRunning(int id) { try { return Process.GetProcessesByName("cmd").Any(x => x.Id == id); } catch (Exception) { return false; } } private void RunAsPrivilegedProcess(Output.OutputWriter output) { using var process = new AugmentedProcess.Process(); var startInfo = new AugmentedProcess.ProcessStartInfo { WindowStyle = ProcessWindowStyle.Normal, FileName = "cmd.exe", Arguments = "/C " + $"{this.Command}", UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, CreateNoWindow = true }; if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } process.StartInfo = startInfo; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(RunAs); if (!Wait) { return; } if (Timeout.HasValue) { var exited = process.WaitForExit(Timeout.Value); if (!exited) { handler.CancelReading(); process.Kill(); throw new TimeoutException($"Command '{Command}' timeout exceeded."); } } else process.WaitForExit(); } ExitCode = Wrap.ExecuteSafe(() => process.ExitCode, true, output.LogOptions).Value; if (ExitCode != 0 && !Command.Contains("ProcessHacker\\x64\\ProcessHacker.exe")) output.WriteLineSafe("Info", $"cmd instance exited with non-zero exit code: {ExitCode}"); if (HandleExitCodes != null) { foreach (string key in HandleExitCodes.Keys) { if (IsApplicableNumber(key, ExitCode.Value)) { throw new ErrorHandlingException(HandleExitCodes[key], $"Command '{Command}' exit code {ExitCode.Value} handled with filter '{key}' --> {HandleExitCodes[key]}."); } } } } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/DownloadAction.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; using Core; using JetBrains.Annotations; using Microsoft.Win32; using Newtonsoft.Json.Linq; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class DownloadAction : TaskActionWithOutputProcessor, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 150; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; public void ResetProgress() { } [YamlMember(typeof(ISOSetting), Alias = "iso")] public override ISOSetting ISO { get; set; } = ISOSetting.True; [YamlMember(typeof(string), Alias = "package")] public string Package { get; set; } = null; [YamlMember(typeof(string), Alias = "destination")] public string Destination { get; set; } = null; [YamlMember(typeof(bool), Alias = "overwrite")] public bool Overwrite { get; set; } = false; [YamlMember(typeof(string), Alias = "url")] public string Url { get; set; } = null; [YamlMember(typeof(string), Alias = "git")] public string Git { get; set; } = null; [YamlMember(typeof(string), Alias = "regex")] public string Regex { get; set; } = null; public string ErrorString() => $"DownloadAction failed to download '{Path.GetFileName(Destination)}'."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { return HasFinished ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; //return GetPackage() == null ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } private bool HasFinished = false; public async Task RunTask(Output.OutputWriter output) { if (Git != null && Url != null) throw new ArgumentException("Cannot specify both Git and Url on DownloadAction"); if (Destination == null) throw new ArgumentException("Destination must be specified on DownloadAction"); if (Git != null && Regex == null) throw new ArgumentException("Regex must be specified with git on DownloadAction"); var dir = AmeliorationUtil.ISO ? $@"{AmeliorationUtil.WimPath}\ProgramData\AME\OOBE\Playbook\Executables" : AmeliorationUtil.Playbook.Path + "\\Executables"; Destination = Environment.ExpandEnvironmentVariables(Destination); var realDestination = Path.IsPathRooted(Destination) ? Destination : Path.Combine(dir, Destination); if (!Directory.Exists(Path.GetDirectoryName(realDestination))) Directory.CreateDirectory(Path.GetDirectoryName(realDestination)!); if (AmeliorationUtil.ISO && Path.IsPathRooted(realDestination)) realDestination = realDestination.Replace(Path.GetPathRoot(realDestination), Path.GetPathRoot(AmeliorationUtil.WimPath)); if (File.Exists(Path.Combine(realDestination))) { if (Overwrite) File.Delete(realDestination); else { output.WriteLineSafe("Info", $"File '{Path.GetFileName(Destination)}' already exists, skipping download. Use 'overwrite: true' to overwrite."); HasFinished = true; return true; } } if (Url != null) await DownloadUrl(output, Url, realDestination); else if (Git != null) await DownloadGit(output, Git, realDestination); output.WriteLineSafe("Info", $"Downloaded file '{Path.GetFileName(Destination)}'"); HasFinished = true; return true; } private async Task DownloadUrl(Output.OutputWriter output, string url, string destination) { output.WriteLineSafe("Info", $"Downloading file from '{url}'..."); var httpClient = new HttpProgressClient(); httpClient.Client.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); await httpClient.StartDownload(url, destination, 300000); } private async Task DownloadGit(Output.OutputWriter output, string git, string destination) { if (Git.Contains("/download/")) throw new ArgumentException("DownloadAction Git link must not be a direct download link. Use url instead, or specify a regex with a base git url."); string releaseUrl = git.Contains("/releases/") ? git.EndsWith("/releases/") ? git + "latest" : git.EndsWith("/releases") ? git + "/latest" : git : git.TrimEnd('/') + "/releases/latest"; string apiReleaseUrl = releaseUrl.Replace("://github.com/", "://api.github.com/repos/"); output.WriteLineSafe("Info", $"Downloading file from '{apiReleaseUrl.TrimEnd('/')}' with filter '{Regex}'..."); using (var httpClient = new HttpProgressClient()) { string downloadUrl = null; long size = 55000000; try { httpClient.Client.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); var response = await httpClient.GetAsync(apiReleaseUrl); response.EnsureSuccessStatusCode(); var releasesContent = await response.Content.ReadAsStringAsync(); var release = JObject.Parse(releasesContent); if (release?.SelectToken("assets") is JArray assets) { var asset = assets.FirstOrDefault(a => System.Text.RegularExpressions.Regex.IsMatch(a["name"].ToString(), Regex)); if (asset != null) { downloadUrl = asset["browser_download_url"]?.ToString(); if (asset["size"] != null) long.TryParse(asset["size"].ToString(), out size); } else { throw new Exception($"No asset found matching regex '{Regex}', but found the following files:\r\n" + string.Join("\r\n", assets.Select(a => a["name"].ToString())));; } } } catch (Exception e) { throw new Exception("Failed to fetch git release info: " + e.Message, e); } if (downloadUrl == null) throw new Exception("Download link unavailable."); // TODO: Add proper progress reporting and figure out how to reliably fetch the file size universally. await httpClient.StartDownload(downloadUrl, destination, size); } } 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.FromMinutes(1) }; } public async Task StartDownload(string downloadUrl, string destinationFilePath, long? size = null) { _downloadUrl = downloadUrl; _destinationFilePath = destinationFilePath; for (int i = 0; i < 10; i++) { await Task.Delay(1000 * i); using (var response = await Client.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) { if (response.StatusCode == HttpStatusCode.ServiceUnavailable) continue; if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.NotFound) { i = 9; continue; } return await DownloadFileFromHttpResponseMessage(response, size); } } throw new Exception("Unexpected end of StartDownload."); } public Task GetAsync(string link) { return Client.GetAsync(link); } private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response, long? size) { response.EnsureSuccessStatusCode(); if (response.Content.Headers.ContentLength.HasValue && response.Content.Headers.ContentLength.Value != 0) size = response.Content.Headers.ContentLength; using (var contentStream = await response.Content.ReadAsStreamAsync()) return 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 md5 = MD5.Create()) { 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; } md5.TransformBlock(buffer, 0, bytesRead, buffer, 0); await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 50 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } md5.TransformFinalBlock(Array.Empty(), 0, 0); return BitConverter.ToString(md5.Hash).Replace("-", "").ToUpper(); } } private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) { if (ProgressChanged == null) return; double? progressPercentage = null; if (totalDownloadSize.HasValue) { progressPercentage = Math.Min(Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2), 100); } ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); } public void Dispose() { Client?.Dispose(); } } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/FileAction.cs ================================================ using System; using System.Collections.Generic; using System.Configuration.Install; 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; using System.Threading.Tasks; using System.Windows; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; using Core; namespace TrustedUninstaller.Shared.Actions { public class FileAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } [YamlMember(typeof(string), Alias = "prioritizeExe")] public bool ExeFirst { get; set; } = false; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 2; [YamlMember(typeof(string), Alias = "useNSudoTI")] public bool TrustedInstaller { get; set; } = false; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"FileAction failed to remove file or directory '{Environment.ExpandEnvironmentVariables(RawPath)}'."; private string GetRealPath() { return AmeliorationUtil.ISO ? Environment.ExpandEnvironmentVariables(RawPath).Replace("C:", AmeliorationUtil.WimPath).Replace("c:", AmeliorationUtil.WimPath) : Environment.ExpandEnvironmentVariables(RawPath); } private string GetRealPath(string path) { return Environment.ExpandEnvironmentVariables(path); } public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) return UninstallTaskStatus.InProgress; if (AmeliorationUtil.ISO) { return UninstallTaskStatus.Completed; } var realPath = GetRealPath(); if (realPath.Contains("*")) { var lastToken = realPath.LastIndexOf("\\"); var parentPath = realPath.Remove(lastToken).TrimEnd('\\'); // This is to prevent it from re-iterating with an incorrect argument if (parentPath.Contains("*")) return UninstallTaskStatus.Completed; var filter = realPath.Substring(lastToken + 1); if (Directory.Exists(parentPath) && (Directory.GetFiles(parentPath, filter).Any() || Directory.GetDirectories(parentPath, filter).Any())) { return UninstallTaskStatus.ToDo; } else return UninstallTaskStatus.Completed; } var isFile = File.Exists(realPath); var isDirectory = Directory.Exists(realPath); 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, Output.OutputWriter output, bool log = false) { if (!TrustedInstaller) { try { File.Delete(file);} catch (Exception e) { } if (File.Exists(file)) { try { EzUnlockFileW(file); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } try { await Task.Run(() => File.Delete(file)); } catch (Exception e) { //Testing.WriteLine(e, "DeleteFile > File.Delete(File)"); } CmdAction delAction = new CmdAction() { Command = $"del /q /f \"{file}\"" }; delAction.RunTaskOnMainThread(output); } } else if (File.Exists("NSudoLC.exe")) { try { var result = EzUnlockFileW(file); //Testing.WriteLine($"ExUnlock on ({file}) result: " + result); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } RunAction tiDelAction = new RunAction() { Exe = "NSudoLC.exe", Arguments = $"-U:T -P:E -M:S -Priority:RealTime -UseCurrentConsole -Wait cmd /c \"del /q /f \"{file}\"\"", BaseDir = true, CreateWindow = false }; tiDelAction.RunTaskOnMainThread(output); } else { Log.WriteSafe(LogType.Warning, "NSudo was invoked with no supplied NSudo executable.", new SerializableTrace(), output.LogOptions); } } private async Task RemoveDirectory(string dir, Output.OutputWriter output, bool log = false) { if (!TrustedInstaller) { try { Directory.Delete(dir, true); } catch { } if (Directory.Exists(dir)) { output.WriteLineSafe("Info", "Directory still exists.. trying second method."); var deleteDirCmd = new CmdAction() { Command = $"rmdir /Q /S \"{dir}\"" }; deleteDirCmd.RunTaskOnMainThread(output); } } else if (File.Exists("NSudoLC.exe")) { RunAction tiDelAction = new RunAction() { Exe = "NSudoLC.exe", Arguments = $"-U:T -P:E -M:S -Priority:RealTime -UseCurrentConsole -Wait cmd /c \"rmdir /q /s \"{dir}\"\"", BaseDir = true, CreateWindow = false }; tiDelAction.RunTaskOnMainThread(output); } else { Log.WriteSafe(LogType.Warning, "NSudo was invoked with no supplied NSudo executable.", new SerializableTrace(), output.LogOptions); } } private async Task DeleteItemsInDirectory(string dir, Output.OutputWriter output, string filter = "*") { var realPath = GetRealPath(dir); if (!Directory.Exists(realPath)) return; var files = Directory.EnumerateFiles(realPath, filter); var directories = Directory.EnumerateDirectories(realPath, filter); if (ExeFirst) files = files.ToList().OrderByDescending(x => x.EndsWith(".exe")); var lockedFilesList = new List { "MpOAV.dll", "MsMpLics.dll", "EppManifest.dll", "MpAsDesc.dll", "MpClient.dll", "MsMpEng.exe" }; foreach (var file in files) { output.WriteLineSafe("Info", $"Deleting {file}..."); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await DeleteFile(file, output); if (File.Exists(file)) { TaskKillAction taskKillAction = new TaskKillAction(); if (file.EndsWith(".sys")) { var driverService = Path.GetFileNameWithoutExtension(file); try { //ServiceAction won't work here due to it not being able to detect driver services. var cmdAction = new CmdAction(); output.WriteLineSafe("Info", $"Removing driver service {driverService}..."); // TODO: Replace with win32 try { ServiceInstaller ServiceInstallerObj = new ServiceInstaller(); ServiceInstallerObj.Context = new InstallContext(); ServiceInstallerObj.ServiceName = driverService; ServiceInstallerObj.Uninstall(null); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); } catch (Exception servException) { Log.WriteExceptionSafe(LogType.Warning, servException, output.LogOptions); } } if (lockedFilesList.Contains(Path.GetFileName(file))) { TaskKillAction killAction = new TaskKillAction() { ProcessName = "MsMpEng" }; await killAction.RunTask(output); killAction.ProcessName = "NisSrv"; await killAction.RunTask(output); killAction.ProcessName = "SecurityHealthService"; await killAction.RunTask(output); killAction.ProcessName = "smartscreen"; await killAction.RunTask(output); } var processes = new List(); try { processes = WinUtil.WhoIsLocking(file); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } var delay = 0; int svcCount = 0; foreach (var svchost in processes.Where(x => x.ProcessName.Equals("svchost"))) { try { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(svchost.Id)) { svcCount++; try { var serviceController = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName.Equals(serviceName)); if (serviceController != null) svcCount += serviceController.DependentServices.Length; } catch (Exception e) { output.WriteLineSafe("Info", $"\r\nError: Could not get amount of dependent services for {serviceName}.\r\nException: " + e.Message); } } } catch (Exception e) { output.WriteLineSafe("Info", $"\r\nError: Could not get amount of services locking file.\r\nException: " + e.Message); } } while (processes.Any() && delay <= 800) { output.WriteLineSafe("Info", "Processes locking the file:"); foreach (var process in processes) { output.WriteLineSafe("Info", process.ProcessName); } if (svcCount > 10) { output.WriteLineSafe("Info", "Amount of locking services exceeds 10, skipping..."); break; } foreach (var process in processes) { try { if (process.ProcessName.Equals("TrustedUninstaller.CLI")) { output.WriteLineSafe("Info", "Skipping TU.CLI..."); continue; } if (Regex.Match(process.ProcessName, "ame.?wizard", RegexOptions.IgnoreCase).Success) { output.WriteLineSafe("Info", "Skipping AME Wizard..."); continue; } taskKillAction.ProcessName = process.ProcessName; taskKillAction.ProcessID = process.Id; output.WriteLineSafe("Info", $"Killing locking process {process.ProcessName} with PID {process.Id}..."); } catch (InvalidOperationException) { // Calling ProcessName on a process object that has exited will thrown this exception causing the // entire loop to abort. Since killing a process takes a bit of time, another process in the loop // could exit during that time. This accounts for that. continue; } try { await taskKillAction.RunTask(output); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } } // This gives any obstinant processes some time to unlock the file on their own. // // This could be done above but it's likely to cause HasExited errors if delays are // introduced after WhoIsLocking. System.Threading.Thread.Sleep(delay); try { processes = WinUtil.WhoIsLocking(file); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } delay += 100; } if (delay >= 800) Log.WriteSafe(LogType.Warning, "Could not kill locking processes for file '{file}'. Process termination loop exceeded max cycles (8).", new SerializableTrace(), output.LogOptions); if (Path.GetExtension(file).Equals(".exe", StringComparison.OrdinalIgnoreCase)) { await new TaskKillAction() { ProcessName = Path.GetFileNameWithoutExtension(file) }.RunTask(output); } await DeleteFile(file, output, true); } } //Loop through any subdirectories foreach (var directory in directories) { //Deletes the content of the directory await DeleteItemsInDirectory(directory, output); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await RemoveDirectory(directory, output, true); if (Directory.Exists(directory)) Log.WriteSafe(LogType.Warning, $"Could not remove directory '{directory}'.", new SerializableTrace(), output.LogOptions); } } public async Task RunTask(Output.OutputWriter output) { if (InProgress) throw new TaskInProgressException("Another File action was called while one was in progress."); InProgress = true; if (AmeliorationUtil.ISO) { var wimPath = Environment.ExpandEnvironmentVariables(RawPath).Replace(@"C:\", "").Replace(@"c:\", ""); AmeliorationUtil.WimInstance.DeleteFileOrFolder(wimPath); return true; } var realPath = GetRealPath(); output.WriteLineSafe("Info", $"Removing file or directory '{realPath}'..."); if (realPath.Contains("*")) { var lastToken = realPath.LastIndexOf("\\"); var parentPath = realPath.Remove(lastToken).TrimEnd('\\'); if (parentPath.Contains("*")) throw new ArgumentException("Parent directories to a given file filter cannot contain wildcards."); var filter = realPath.Substring(lastToken + 1); await DeleteItemsInDirectory(parentPath, output, filter); InProgress = false; return true; } var isFile = File.Exists(realPath); var isDirectory = Directory.Exists(realPath); if (isDirectory) { System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await RemoveDirectory(realPath, output); if (Directory.Exists(realPath)) { CmdAction permAction = new CmdAction() { Command = $"takeown /f \"{realPath}\" /r /d Y>NUL & icacls \"{realPath}\" /t /grant Administrators:F /c > NUL", Timeout = 5000 }; try { permAction.RunTaskOnMainThread(output); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } try { if (realPath.Contains("Defender")) { TaskKillAction killAction = new TaskKillAction() { ProcessName = "MsMpEng" }; await killAction.RunTask(output); killAction.ProcessName = "NisSrv"; await killAction.RunTask(output); killAction.ProcessName = "SecurityHealthService"; await killAction.RunTask(output); killAction.ProcessName = "smartscreen"; await killAction.RunTask(output); } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } await RemoveDirectory(realPath, output, true); if (Directory.Exists(realPath)) { //Delete the files in the initial directory. DOES delete directories. await DeleteItemsInDirectory(realPath, output); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await RemoveDirectory(realPath, output, true); } } } if (isFile) { try { var lockedFilesList = new List { "MpOAV.dll", "MsMpLics.dll", "EppManifest.dll", "MpAsDesc.dll", "MpClient.dll", "MsMpEng.exe" }; var fileName = realPath.Split('\\').LastOrDefault(); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); await DeleteFile(realPath, output); if (File.Exists(realPath)) { CmdAction permAction = new CmdAction() { Command = $"takeown /f \"{realPath}\" /r /d Y>NUL & icacls \"{realPath}\" /t /grant Administrators:F /c > NUL", Timeout = 5000 }; try { permAction.RunTaskOnMainThread(output); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } TaskKillAction taskKillAction = new TaskKillAction(); if (realPath.EndsWith(".sys")) { var driverService = Path.GetFileNameWithoutExtension(realPath); try { //ServiceAction won't work here due to it not being able to detect driver services. var cmdAction = new CmdAction(); output.WriteLineSafe("Info", $"Removing driver service {driverService}..."); // TODO: Replace with win32 try { ServiceInstaller ServiceInstallerObj = new ServiceInstaller(); ServiceInstallerObj.Context = new InstallContext(); ServiceInstallerObj.ServiceName = driverService; ServiceInstallerObj.Uninstall(null); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } WinUtil.CheckKph(); cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction stop"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {driverService} -caction delete"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); } catch (Exception servException) { Log.WriteExceptionSafe(LogType.Warning, servException, output.LogOptions); } } if (lockedFilesList.Contains(fileName)) { TaskKillAction killAction = new TaskKillAction() { ProcessName = "MsMpEng" }; await killAction.RunTask(output); killAction.ProcessName = "NisSrv"; await killAction.RunTask(output); killAction.ProcessName = "SecurityHealthService"; await killAction.RunTask(output); killAction.ProcessName = "smartscreen"; await killAction.RunTask(output); } var processes = new List(); try { processes = WinUtil.WhoIsLocking(realPath); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } var delay = 0; int svcCount = 0; foreach (var svchost in processes.Where(x => x.ProcessName.Equals("svchost"))) { try { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(svchost.Id)) { svcCount++; try { var serviceController = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName.Equals(serviceName)); if (serviceController != null) svcCount += serviceController.DependentServices.Length; } catch (Exception e) { output.WriteLineSafe("Warning", $"\r\nError: Could not get amount of dependent services for {serviceName}.\r\nException: " + e.Message); } } } catch (Exception e) { output.WriteLineSafe("Warning", $"\r\nError: Could not get amount of services locking file.\r\nException: " + e.Message); } } if (svcCount > 8) output.WriteLineSafe("Info", "Amount of locking services exceeds 8, skipping..."); while (processes.Any() && delay <= 800 && svcCount <= 8) { output.WriteLineSafe("Info", "Processes locking the file:"); foreach (var process in processes) { output.WriteLineSafe("Info", process.ProcessName); } foreach (var process in processes) { try { if (process.ProcessName.Equals("TrustedUninstaller.CLI")) { output.WriteLineSafe("Info", "Skipping TU.CLI..."); continue; } if (Regex.Match(process.ProcessName, "ame.?wizard", RegexOptions.IgnoreCase).Success) { output.WriteLineSafe("Info", "Skipping AME Wizard..."); continue; } taskKillAction.ProcessName = process.ProcessName; taskKillAction.ProcessID = process.Id; output.WriteLineSafe("Info", $"Killing {process.ProcessName} with PID {process.Id}... it is locking {realPath}"); } catch (InvalidOperationException) { // Calling ProcessName on a process object that has exited will thrown this exception causing the // entire loop to abort. Since killing a process takes a bit of time, another process in the loop // could exit during that time. This accounts for that. continue; } try { await taskKillAction.RunTask(output); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } } // This gives any obstinant processes some time to unlock the file on their own. // // This could be done above but it's likely to cause HasExited errors if delays are // introduced after WhoIsLocking. System.Threading.Thread.Sleep(delay); try { processes = WinUtil.WhoIsLocking(realPath); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } delay += 100; } if (delay >= 800) Log.WriteSafe(LogType.Warning, "Could not kill locking processes for file '{realPath}'. Process termination loop exceeded max cycles (8).", new SerializableTrace(), output.LogOptions); if (Path.GetExtension(realPath).Equals(".exe", StringComparison.OrdinalIgnoreCase)) { await new TaskKillAction() { ProcessName = Path.GetFileNameWithoutExtension(realPath) }.RunTask(output); } await DeleteFile(realPath, output, true); } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); } } else { output.WriteLineSafe("Info", $"File or directory '{realPath}' not found."); } InProgress = false; return true; } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/LanguageAction.cs ================================================ using System; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; using Core; using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.Shared.Actions { class LanguageAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string Tag { get; set; } = ""; public string Primary() => "for language " + Tag; public bool Display { get; set; } = false; public string ErrorString() => $"LanguageAction failed to install language {Tag}."; [DllImport("intl.cpl", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int IntlUpdateSystemLocale(string LocaleName, int dwFlags); public UninstallTaskStatus GetStatus(Output.OutputWriter output) => InputLanguage.InstalledInputLanguages.Cast() .Any(c => c.Culture.IetfLanguageTag == this.Tag) ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; public async Task RunTask(Output.OutputWriter output) { if (GetStatus(output) != UninstallTaskStatus.ToDo) { return false; } if (this.Display) { // Reversed from the Set-WinSystemLocale cmdlet... // TODO: Figure out the return value lol IntlUpdateSystemLocale(this.Tag, 2); } // TODO: Installing languages is AIDS return false; } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/LineInFileAction.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Core; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { internal enum LineInFileOperation { Delete = 0, Add = 1 } internal class LineInFileAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(Alias = "path")] public string RawPath { get; set; } [YamlMember(Alias = "line")] public string RawLines { get; set; } [YamlMember(Alias = "operation")] public LineInFileOperation Operation { get; set; } = LineInFileOperation.Delete; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } = false; public void ResetProgress() => InProgress = false; public string ErrorString() => $"LineInFileAction failed to {Operation.ToString().ToLower()} lines to file '{RawPath}'."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } var realPath = this.GetRealPath(); if (!File.Exists(realPath)) { // If the file doesn't exist it can't contain the lines either, can it? return Operation == LineInFileOperation.Delete ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } var isDone = !GetMissingLines().Any(); return isDone ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } private IEnumerable GetLines() => RawLines.Split( new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None ); private IEnumerable GetMissingLines() { var realPath = this.GetRealPath(); var fileLines = File.ReadAllLines(realPath); var targetLines = GetLines(); return targetLines.Where(line => !fileLines.Contains(line)); } private string GetRealPath() { return Environment.ExpandEnvironmentVariables(RawPath); } public async Task RunTask(Output.OutputWriter output) { if (InProgress) throw new TaskInProgressException("Another LineInFile action was called while one was in progress."); InProgress = true; //Wording should be improved here output.WriteLineSafe("Info", $"{Operation.ToString().TrimEnd('e')}ing text lines in file '{RawPath}'..."); var realPath = this.GetRealPath(); var missingLines = GetMissingLines(); using var sw = File.AppendText(realPath); foreach (var missingLine in missingLines) { await sw.WriteLineAsync(missingLine); } InProgress = false; return true; } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/PowershellAction.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Core; using JetBrains.Annotations; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class PowerShellAction : Tasks.TaskActionWithOutputProcessor, ITaskAction { public override string? IsISOCompatible() => "For safety reasons, PowerShellAction does not support iso."; public void RunTaskOnMainThread(Output.OutputWriter output) { if (InProgress) throw new TaskInProgressException("Another Powershell action was called while one was in progress."); InProgress = true; var privilegeText = RunAs == Privilege.CurrentUser ? " as the current user" : RunAs == Privilege.CurrentUserElevated ? " as the current user elevated" : RunAs == Privilege.System ? " as the system account" : ""; output.WriteLineSafe("Info", $"Running PowerShell command '{Command}'{privilegeText}..."); WinUtil.CheckKph(); if (RunAs == Privilege.TrustedInstaller) RunAsProcess(output); else RunAsPrivilegedProcess(output); InProgress = false; return; } [YamlMember(typeof(Privilege), Alias = "runas")] public Privilege RunAs { get; set; } = Privilege.TrustedInstaller; [YamlMember(typeof(string), Alias = "command")] public string Command { get; set; } [YamlMember(typeof(string), Alias = "timeout")] public int? Timeout { get; set; } [YamlMember(typeof(string), Alias = "wait")] public bool Wait { get; set; } = true; [YamlMember(typeof(bool), Alias = "exeDir")] public bool ExeDir { get; set; } = false; [YamlMember(typeof(Dictionary), Alias = "handleExitCodes")] [CanBeNull] public Dictionary HandleExitCodes { get; set; } = null; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; private int? ExitCode { get; set; } public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"PowerShellAction failed to run command '{Command}'."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } return ExitCode == null ? UninstallTaskStatus.ToDo: UninstallTaskStatus.Completed; } public Task RunTask(Output.OutputWriter output) { return null; } private void RunAsProcess(Output.OutputWriter output) { using var process = new Process(); var startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Normal, FileName = "PowerShell.exe", Arguments = $@"-NoP -ExecutionPolicy Bypass -NonInteractive -C ""{Command}""", UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, CreateNoWindow = true }; if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } process.StartInfo = startInfo; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(RunAs); if (!Wait) { return; } if (Timeout.HasValue) { var exited = process.WaitForExit(Timeout.Value); if (!exited) { handler.CancelReading(); process.Kill(); throw new TimeoutException($"Executable run timeout exceeded."); } } else { bool exited = process.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && PowerShellRunning(process.Id)) { exited = process.WaitForExit(30000); } } } ExitCode = Wrap.ExecuteSafe(() => process.ExitCode, true, output.LogOptions).Value; if (ExitCode != 0) output.WriteLineSafe("Info", $"PowerShell instance exited with error code: {ExitCode}"); if (HandleExitCodes != null) { foreach (string key in HandleExitCodes.Keys) { if (IsApplicableNumber(key, ExitCode.Value)) { throw new ErrorHandlingException(HandleExitCodes[key], $"PowerShell command '{Command}' exit code {ExitCode.Value} handled with filter '{key}' --> {HandleExitCodes[key]}."); } } } } private static bool PowerShellRunning(int id) { try { return Process.GetProcessesByName("powershell").Any(x => x.Id == id); } catch (Exception) { return false; } } private void RunAsPrivilegedProcess(Output.OutputWriter output) { using var process = new AugmentedProcess.Process(); var startInfo = new AugmentedProcess.ProcessStartInfo { WindowStyle = ProcessWindowStyle.Normal, FileName = "PowerShell.exe", Arguments = $@"-NoP -ExecutionPolicy Bypass -NonInteractive -C ""{Command}""", UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, CreateNoWindow = true }; if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } process.StartInfo = startInfo; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(RunAs); if (!Wait) { return; } if (Timeout.HasValue) { var exited = process.WaitForExit(Timeout.Value); if (!exited) { handler.CancelReading(); process.Kill(); throw new TimeoutException($"Executable run timeout exceeded."); } } else process.WaitForExit(); } ExitCode = Wrap.ExecuteSafe(() => process.ExitCode, true, output.LogOptions).Value; if (ExitCode != 0) output.WriteLineSafe("Info", $"PowerShell instance exited with error code: {ExitCode}"); if (HandleExitCodes != null) { foreach (string key in HandleExitCodes.Keys) { if (IsApplicableNumber(key, ExitCode.Value)) { throw new ErrorHandlingException(HandleExitCodes[key], $"PowerShell command '{Command}' exit code {ExitCode.Value} handled with filter '{key}' --> {HandleExitCodes[key]}."); } } } } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using Core; using Microsoft.Win32; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public enum RegistryKeyOperation { Delete = 0, Add = 1 } public class RegistryKeyAction : TaskActionWithOutputProcessor, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } [YamlMember(typeof(Scope), Alias = "scope")] public Scope Scope { get; set; } = Scope.AllUsers; [YamlMember(typeof(RegistryKeyOperation), Alias = "operation")] public RegistryKeyOperation Operation { get; set; } = RegistryKeyOperation.Delete; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; static Dictionary HiveKeys = new Dictionary { { RegistryHive.ClassesRoot, new UIntPtr(0x80000000u) }, { RegistryHive.CurrentConfig, new UIntPtr(0x80000005u) }, { RegistryHive.CurrentUser, new UIntPtr(0x80000001u) }, { RegistryHive.DynData, new UIntPtr(0x80000006u) }, { RegistryHive.LocalMachine, new UIntPtr(0x80000002u) }, { RegistryHive.PerformanceData, new UIntPtr(0x80000004u) }, { RegistryHive.Users, new UIntPtr(0x80000003u) } }; [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult); [DllImport("advapi32.dll", EntryPoint = "RegDeleteKeyEx", SetLastError = true)] private static extern int RegDeleteKeyEx( UIntPtr hKey, string lpSubKey, uint samDesired, // see Notes below uint Reserved); private static void DeleteKeyTreeWin32(string key, RegistryHive hive) { var openedKey = RegistryKey.OpenBaseKey(hive, RegistryView.Default).OpenSubKey(key); if (openedKey == null) return; openedKey.GetSubKeyNames().ToList().ForEach(subKey => DeleteKeyTreeWin32(key + "\\" + subKey, hive)); openedKey.Close(); RegDeleteKeyEx(HiveKeys[hive], key, 0x0100, 0); } private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"RegistryKeyAction failed to {Operation.ToString().ToLower()} key '{KeyName}'."; private List GetRoots(ref string subKey) { var hive = KeyName.Split('\\').GetValue(0).ToString().ToUpper(); var list = new List(); if (hive.Equals("HKCU") || hive.Equals("HKEY_CURRENT_USER")) { if (AmeliorationUtil.ISO) { if ((hive == "HKCU" || hive == "HKEY_CURRENT_USER") && subKey.StartsWith(@"Software\Classes", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(GetSubKey(subKey)); return new List() { Registry.Users.OpenSubKey("HKCU_Classes-" + AmeliorationUtil.ISOGuid, true) }; } else return new List() { Registry.Users.OpenSubKey("HKCU-" + AmeliorationUtil.ISOGuid, true) }; } RegistryKey usersKey; List userKeys; switch (Scope) { case Scope.AllUsers: WinUtil.RegistryManager.HookUserHives(); usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); userKeys = usersKey.GetSubKeyNames(). Where(x => x.StartsWith("S-") && usersKey.OpenSubKey(x).GetSubKeyNames().Any(y => y.Equals("Volatile Environment"))).ToList(); userKeys.AddRange(usersKey.GetSubKeyNames().Where(x => x.StartsWith("AME_UserHive_") && !x.EndsWith("_Classes")).ToList()); userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true))); return list; case Scope.ActiveUsers: usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); userKeys = usersKey.GetSubKeyNames(). Where(x => x.StartsWith("S-") && usersKey.OpenSubKey(x).GetSubKeyNames().Any(y => y.Equals("Volatile Environment"))).ToList(); userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true))); return list; case Scope.DefaultUser: usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); userKeys = usersKey.GetSubKeyNames().Where(x => x.Equals("AME_UserHive_Default") && !x.EndsWith("_Classes")).ToList(); userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true))); return list; } } if (AmeliorationUtil.ISO) { if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SAM\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SAM-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SECURITY\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SECURITY-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SOFTWARE\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SOFTWARE-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SYSTEM\CurrentControlSet\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(GetSubKey(subKey)); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SYSTEM-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x + @"\ControlSet001", true))); } else if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SYSTEM\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SYSTEM-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKCR" || hive == "HKEY_CLASSES_ROOT")) { list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SOFTWARE-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x + @"\Classes", true))); } if ((hive == "HKU" || hive == "HKEY_USERS") && (subKey.StartsWith(@"S-1-5-18\", StringComparison.OrdinalIgnoreCase) || subKey.StartsWith(@".DEFAULT\", StringComparison.OrdinalIgnoreCase))) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKU-S-1-5-18-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKU" || hive == "HKEY_USERS") && subKey.StartsWith(@"S-1-5-19", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKU-S-1-5-19-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKU" || hive == "HKEY_USERS") && subKey.StartsWith(@"S-1-5-20", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKU-S-1-5-20-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } } else { list.Add(hive switch { "HKCU" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKEY_CURRENT_USER" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKLM" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKEY_LOCAL_MACHINE" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKCR" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKEY_CLASSES_ROOT" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKU" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), "HKEY_USERS" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), _ => throw new ArgumentException($"Key '{KeyName}' does not specify a valid registry hive.") }); } return list; } public string GetSubKey(string key) => key.Replace("\\\\", "\\").Substring(key.IndexOf('\\') + 1); public UninstallTaskStatus GetStatus(Output.OutputWriter output) { try { var subKey = GetSubKey(KeyName); var roots = GetRoots(ref subKey); foreach (var _root in roots) { var root = _root; try { if (root.Name.Contains("AME_UserHive_") && subKey.StartsWith("SOFTWARE\\Classes", StringComparison.CurrentCultureIgnoreCase)) { var usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); var name = (root.Name.Contains("AME_UserHive_Default") ? "AME_UserHive_Default" : root.Name.Substring(11)) + "_Classes"; root.Dispose(); root = usersKey.OpenSubKey(name, true); subKey = Regex.Replace(subKey, @"^SOFTWARE\\*Classes\\*", "", RegexOptions.IgnoreCase); if (root == null) { continue; } } using var openedSubKey = root.OpenSubKey(subKey); if (Operation == RegistryKeyOperation.Delete && openedSubKey != null) { return UninstallTaskStatus.ToDo; } if (Operation == RegistryKeyOperation.Add && openedSubKey == null) { return UninstallTaskStatus.ToDo; } } finally { root?.Dispose(); } } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); return UninstallTaskStatus.ToDo; } return UninstallTaskStatus.Completed; } public async Task RunTask(Output.OutputWriter output) { output.WriteLineSafe("Info", $"{Operation.ToString().TrimEnd('e')}ing registry key '{KeyName}'..."); var subKey = GetSubKey(KeyName); var roots = GetRoots(ref subKey); foreach (var _root in roots) { var root = _root; try { try { if (root.Name.Contains("AME_UserHive_") && subKey.StartsWith("SOFTWARE\\Classes", StringComparison.CurrentCultureIgnoreCase)) { var usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); var name = root.Name.Substring(11) + "_Classes"; root.Dispose(); root = usersKey.OpenSubKey(name, true); subKey = Regex.Replace(subKey, @"^SOFTWARE\\*Classes\\*", "", RegexOptions.IgnoreCase); if (root == null) { Log.WriteSafe(LogType.Warning, "User classes hive not found for hive {_root.Name}.", new SerializableTrace(), output.LogOptions); continue; } } if (Operation == RegistryKeyOperation.Add) { using var _ = root.CreateSubKey(subKey); } if (Operation == RegistryKeyOperation.Delete) { try { root.DeleteSubKeyTree(subKey, false); } catch (Exception e) { if (AmeliorationUtil.ISO) throw; Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); var rootHive = root.Name.Split('\\').First() switch { "HKEY_CURRENT_USER" => RegistryHive.CurrentUser, "HKEY_LOCAL_MACHINE" => RegistryHive.LocalMachine, "HKEY_CLASSES_ROOT" => RegistryHive.ClassesRoot, "HKEY_USERS" => RegistryHive.Users, _ => throw new ArgumentException($"Unable to parse: " + root.Name.Split('\\').First()) }; DeleteKeyTreeWin32(root.Name.StartsWith("HKEY_USERS") ? root.Name.Split('\\')[1] + "\\" + subKey: subKey, rootHive); } } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); if (e is UnauthorizedAccessException) { try { var tempPath = Environment.ExpandEnvironmentVariables(@"%TEMP%\AME"); var regPath = Environment.ExpandEnvironmentVariables(@"%SYSTEMROOT%\System32\reg.exe"); var ameRegPath = Path.Combine(tempPath, "amereg.exe"); if (File.Exists(regPath)) { if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); File.Copy(regPath, ameRegPath); RegAddKey(output, ameRegPath, root!.Name + "\\" + subKey); File.Delete(ameRegPath); } else { output.WriteLineSafe("Info", "reg.exe not found, cannot try alternate method."); } } catch (Exception exception) { Log.WriteExceptionSafe(LogType.Warning, exception, output.LogOptions); } } } } finally { root?.Dispose(); } } return true; } private void RegAddKey(Output.OutputWriter output, string exePath, string key) { var arguments = @$"add ""{key}"" /f"; var startInfo = new ProcessStartInfo { CreateNoWindow = false, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = true, RedirectStandardOutput = true, FileName = exePath, Arguments = arguments, }; using var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(); bool exited = process.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(process.ProcessName, process.Id)) { exited = process.WaitForExit(30000); } } int exitCode = Wrap.ExecuteSafe(() => process.ExitCode, true, output.LogOptions).Value; if (exitCode != 0) output.WriteLineSafe("Warning", $"Reg exited with a non-zero exit code: {exitCode}"); } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/RegistryValueAction.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Security; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using Core; using Microsoft.Win32; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public enum RegistryValueOperation { Delete = 0, Add = 1, // This indicates to skip the action if the specified value does not already exist Set = 2 } public enum RegistryValueType { REG_SZ = RegistryValueKind.String, REG_MULTI_SZ = RegistryValueKind.MultiString, REG_EXPAND_SZ = RegistryValueKind.ExpandString, REG_DWORD = RegistryValueKind.DWord, REG_QWORD = RegistryValueKind.QWord, REG_BINARY = RegistryValueKind.Binary, REG_NONE = RegistryValueKind.None, REG_UNKNOWN = RegistryValueKind.Unknown } public class RegistryValueAction : TaskActionWithOutputProcessor, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } [YamlMember(typeof(string), Alias = "value")] public string Value { get; set; } = ""; [YamlMember(typeof(object), Alias = "data")] public object? Data { get; set; } [YamlMember(typeof(RegistryValueType), Alias = "type")] public RegistryValueType Type { get; set; } [YamlMember(typeof(Scope), Alias = "scope")] public Scope Scope { get; set; } = Scope.AllUsers; [YamlMember(typeof(RegistryValueOperation), Alias = "operation")] public RegistryValueOperation Operation { get; set; } = RegistryValueOperation.Add; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() { /* int roots; try { roots = GetRoots().Count; } catch (Exception e) { roots = 1; } */ return ProgressWeight; } public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"RegistryValueAction failed to {Operation.ToString().ToLower()} value '{Value}' in key '{KeyName}'"; private List GetRoots(ref string subKey) { var hive = KeyName.Split('\\').GetValue(0).ToString().ToUpper(); var list = new List(); if (hive.Equals("HKCU") || hive.Equals("HKEY_CURRENT_USER")) { if (AmeliorationUtil.ISO) { if ((hive == "HKCU" || hive == "HKEY_CURRENT_USER") && subKey.StartsWith(@"Software\Classes", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(GetSubKey(subKey)); return new List() { Registry.Users.OpenSubKey("HKCU_Classes-" + AmeliorationUtil.ISOGuid, true) }; } else return new List() { Registry.Users.OpenSubKey("HKCU-" + AmeliorationUtil.ISOGuid, true) }; } RegistryKey usersKey; List userKeys; switch (Scope) { case Scope.AllUsers: WinUtil.RegistryManager.HookUserHives(); usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); userKeys = usersKey.GetSubKeyNames(). Where(x => x.StartsWith("S-") && usersKey.OpenSubKey(x).GetSubKeyNames().Any(y => y.Equals("Volatile Environment"))).ToList(); userKeys.AddRange(usersKey.GetSubKeyNames().Where(x => x.StartsWith("AME_UserHive_") && !x.EndsWith("_Classes")).ToList()); userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true))); return list; case Scope.ActiveUsers: usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); userKeys = usersKey.GetSubKeyNames(). Where(x => x.StartsWith("S-") && usersKey.OpenSubKey(x).GetSubKeyNames().Any(y => y.Equals("Volatile Environment"))).ToList(); userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true))); return list; case Scope.DefaultUser: usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); userKeys = usersKey.GetSubKeyNames().Where(x => x.Equals("AME_UserHive_Default") && !x.EndsWith("_Classes")).ToList(); userKeys.ForEach(x => list.Add(usersKey.OpenSubKey(x, true))); return list; } } if (AmeliorationUtil.ISO) { if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SAM\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SAM-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SECURITY\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SECURITY-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SOFTWARE\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SOFTWARE-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SYSTEM\CurrentControlSet\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(GetSubKey(subKey)); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SYSTEM-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x + @"\ControlSet001", true))); } else if ((hive == "HKLM" || hive == "HKEY_LOCAL_MACHINE") && subKey.StartsWith(@"SYSTEM\", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SYSTEM-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKCR" || hive == "HKEY_CLASSES_ROOT")) { list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SOFTWARE-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x + @"\Classes", true))); } if ((hive == "HKU" || hive == "HKEY_USERS") && (subKey.StartsWith(@"S-1-5-18\", StringComparison.OrdinalIgnoreCase) || subKey.StartsWith(@".DEFAULT\", StringComparison.OrdinalIgnoreCase))) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKU-S-1-5-18-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKU" || hive == "HKEY_USERS") && subKey.StartsWith(@"S-1-5-19", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKU-S-1-5-19-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } if ((hive == "HKU" || hive == "HKEY_USERS") && subKey.StartsWith(@"S-1-5-20", StringComparison.OrdinalIgnoreCase)) { subKey = GetSubKey(subKey); list.AddRange(Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKU-S-1-5-20-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))); } } else { list.Add(hive switch { "HKCU" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKEY_CURRENT_USER" => RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default), "HKLM" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKEY_LOCAL_MACHINE" => RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default), "HKCR" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKEY_CLASSES_ROOT" => RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default), "HKU" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), "HKEY_USERS" => RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default), _ => throw new ArgumentException($"Key '{KeyName}' does not specify a valid registry hive.") }); } return list; } public string GetSubKey(string key) => key.Replace("\\\\", "\\").Substring(key.IndexOf('\\') + 1); //public object? GetCurrentValue(RegistryKey root) //{ // var subkey = GetSubKey(); // return Registry.GetValue(root.Name + "\\" + subkey, Value, null); //} public static byte[] StringToByteArray(string hex) { return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .ToArray(); } public UninstallTaskStatus GetStatus(Output.OutputWriter output) { try { var subKey = GetSubKey(KeyName); var roots = GetRoots(ref subKey); foreach (var _root in roots) { var root = _root; try { if (root.Name.Contains("AME_UserHive_") && subKey.StartsWith("SOFTWARE\\Classes", StringComparison.CurrentCultureIgnoreCase)) { var usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); var name = root.Name.Substring(11) + "_Classes"; root.Dispose(); root = usersKey.OpenSubKey(name, true); subKey = Regex.Replace(subKey, @"^SOFTWARE\\*Classes\\*", "", RegexOptions.IgnoreCase); if (root == null) { continue; } } using var openedSubKey = root.OpenSubKey(subKey); if (openedSubKey == null && (Operation == RegistryValueOperation.Set || Operation == RegistryValueOperation.Delete)) continue; if (openedSubKey == null) return UninstallTaskStatus.ToDo; var value = openedSubKey.GetValue(Value); if (value == null) { if (Operation == RegistryValueOperation.Set || Operation == RegistryValueOperation.Delete) continue; return UninstallTaskStatus.ToDo; } if (Operation == RegistryValueOperation.Delete) return UninstallTaskStatus.ToDo; if (Data == null) return UninstallTaskStatus.ToDo; bool matches; try { matches = Type switch { RegistryValueType.REG_SZ => Data.ToString() == value.ToString(), RegistryValueType.REG_EXPAND_SZ => // RegistryValueOptions.DoNotExpandEnvironmentNames above did not seem to work. Environment.ExpandEnvironmentVariables(Data.ToString()) == value.ToString(), RegistryValueType.REG_MULTI_SZ => Data.ToString() == "" ? ((string[])value).SequenceEqual(new string[] { }) : ((string[])value).SequenceEqual(Data.ToString().Split(new string[] { "\\0" }, StringSplitOptions.None)), RegistryValueType.REG_DWORD => unchecked((int)Convert.ToUInt32(Data)) == (int)value, RegistryValueType.REG_QWORD => Convert.ToUInt64(Data) == (ulong)value, RegistryValueType.REG_BINARY => ((byte[])value).SequenceEqual(StringToByteArray(Data.ToString())), RegistryValueType.REG_NONE => ((byte[])value).SequenceEqual(new byte[0]), RegistryValueType.REG_UNKNOWN => Data.ToString() == value.ToString(), _ => throw new ArgumentException("Impossible.") }; } catch (InvalidCastException) { matches = false; } if (!matches) return UninstallTaskStatus.ToDo; } finally { root?.Dispose(); } } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); return UninstallTaskStatus.ToDo; } return UninstallTaskStatus.Completed; } public async Task RunTask(Output.OutputWriter output) { output.WriteLineSafe("Info", $"{Operation.ToString().TrimEnd('e')}ing value '{Value}' in key '{KeyName}'..."); var subKey = GetSubKey(KeyName); var roots = GetRoots(ref subKey); foreach (var _root in roots) { var root = _root; try { try { if (root.Name.Contains("AME_UserHive_") && subKey.StartsWith("SOFTWARE\\Classes", StringComparison.CurrentCultureIgnoreCase)) { var usersKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Default); var name = (root.Name.Contains("AME_UserHive_Default") ? "AME_UserHive_Default" : root.Name.Substring(11)) + "_Classes"; root.Dispose(); root = usersKey.OpenSubKey(name, true); subKey = Regex.Replace(subKey, @"^SOFTWARE\\*Classes\\*", "", RegexOptions.IgnoreCase); if (root == null) { Log.WriteSafe(LogType.Warning, $"User classes hive not found: " + name, new SerializableTrace(), output.LogOptions); continue; } } //if (GetCurrentValue(root) == Data) continue; using (var openedKey = root.OpenSubKey(subKey)) { if (openedKey == null && Operation == RegistryValueOperation.Set) continue; if (openedKey == null && Operation == RegistryValueOperation.Add) { using var _ = root.CreateSubKey(subKey); } } if (Operation == RegistryValueOperation.Delete) { using var key = root.OpenSubKey(subKey, true); key?.DeleteValue(Value, false); continue; } if (Type == RegistryValueType.REG_BINARY) { var data = StringToByteArray(Data.ToString()); Registry.SetValue(root.Name + "\\" + subKey, Value, data, (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_DWORD) { // DWORD values using the highest bit set fail without this, for example '2962489444'. // See https://stackoverflow.com/questions/6608400/how-to-put-a-dword-in-the-registry-with-the-highest-bit-set; var value = unchecked((int)Convert.ToUInt32(Data)); Registry.SetValue(root.Name + "\\" + subKey, Value, value, (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_QWORD) { Registry.SetValue(root.Name + "\\" + subKey, Value, Convert.ToUInt64(Data), (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_NONE) { byte[] none = new byte[0]; Registry.SetValue(root.Name + "\\" + subKey, Value, none, (RegistryValueKind)Type); } else if (Type == RegistryValueType.REG_MULTI_SZ) { string[] data; if (Data.ToString() == "") data = new string[] { }; else data = Data.ToString().Split(new string[] { "\\0" }, StringSplitOptions.None); Registry.SetValue(root.Name + "\\" + subKey, Value, data, (RegistryValueKind)Type); } else { Registry.SetValue(root.Name + "\\" + subKey, Value, Data, (RegistryValueKind)Type); } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, output.LogOptions); if (e is UnauthorizedAccessException) { try { var tempPath = Environment.ExpandEnvironmentVariables(@"%TEMP%\AME"); var regPath = Environment.ExpandEnvironmentVariables(@"%SYSTEMROOT%\System32\reg.exe"); var ameRegPath = Path.Combine(tempPath, "amereg.exe"); if (File.Exists(regPath)) { if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath); File.Copy(regPath, ameRegPath); RegAddValue(output, ameRegPath, root!.Name + "\\" + subKey, Value, Type, Data?.ToString() ?? null); File.Delete(ameRegPath); } else { output.WriteLineSafe("Info", "reg.exe not found, cannot try alternate method."); } } catch (Exception exception) { Log.WriteExceptionSafe(LogType.Warning, exception, output.LogOptions); } } } } finally { root?.Dispose(); } } return true; } private void RegAddValue(Output.OutputWriter output, string exePath, string key, string value, RegistryValueType type, string? data) { var arguments = @$"add ""{key}"" /v ""{value}"" /t ""{type.ToString()}"" /d ""{data.ToString()}"" /f"; var startInfo = new ProcessStartInfo { CreateNoWindow = false, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = true, RedirectStandardOutput = true, FileName = exePath, Arguments = arguments, }; using var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(); bool exited = process.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(process.ProcessName, process.Id)) { exited = process.WaitForExit(30000); } } int exitCode = Wrap.ExecuteSafe(() => process.ExitCode, true, output.LogOptions).Value; if (exitCode != 0) output.WriteLineSafe("Warning", $"Reg exited with a non-zero exit code: {exitCode}"); } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/RunAction.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Documents; using Core; using JetBrains.Annotations; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class RunAction : Tasks.TaskActionWithOutputProcessor, ITaskAction { public override string? IsISOCompatible() => "For safety reasons, RunAction does not support iso."; public void RunTaskOnMainThread(Output.OutputWriter output) { if (RawPath != null) RawPath = Environment.ExpandEnvironmentVariables(RawPath); InProgress = true; var privilegeText = RunAs == Privilege.CurrentUser ? " as the current user" : RunAs == Privilege.CurrentUserElevated ? " as the current user elevated" : RunAs == Privilege.System ? " as the system account" : RunAs == Privilege.CurrentUserTrustedInstaller ? " as the current user trusted installer" : ""; if (Arguments == null) output.WriteLineSafe("Info", $"Running '{Exe + privilegeText}'..."); else output.WriteLineSafe("Info", $"Running '{Exe}' with arguments '{Arguments + "'" + privilegeText}..."); WinUtil.CheckKph(); var currentDir = Directory.GetCurrentDirectory(); if (ExeDir) RawPath = AmeliorationUtil.Playbook.Path + "\\Executables"; if (BaseDir) RawPath = currentDir; string file = null; if (RawPath != null && File.Exists(Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe))) file = Path.Combine(Environment.ExpandEnvironmentVariables(RawPath), Exe); else if (ExistsInPath(Exe) || File.Exists(Environment.ExpandEnvironmentVariables(Exe))) file = Environment.ExpandEnvironmentVariables(Exe); if (file == null) throw new FileNotFoundException($"Executable not found."); if (RunAs == Privilege.TrustedInstaller) RunAsProcess(file, output); else RunAsPrivilegedProcess(file, output); InProgress = false; return; } [YamlMember(typeof(Privilege), Alias = "runas")] public Privilege RunAs { get; set; } = Privilege.TrustedInstaller; [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } = null; [YamlMember(typeof(string), Alias = "exe")] public string Exe { get; set; } [YamlMember(typeof(string), Alias = "args")] public string? Arguments { get; set; } [YamlMember(typeof(bool), Alias = "baseDir")] public bool BaseDir { get; set; } = false; [YamlMember(typeof(bool), Alias = "exeDir")] public bool ExeDir { get; set; } = false; [YamlMember(typeof(bool), Alias = "createWindow")] public bool CreateWindow { get; set; } = false; [YamlMember(typeof(bool), Alias = "hideWindow")] public bool HideWindow { get; set; } = false; [YamlMember(typeof(bool), Alias = "showOutput")] public bool ShowOutput { get; set; } = true; [YamlMember(typeof(bool), Alias = "showError")] public bool ShowError { get; set; } = true; [YamlMember(typeof(int), Alias = "timeout")] public int? Timeout { get; set; } [YamlMember(typeof(string), Alias = "wait")] public bool Wait { get; set; } = true; [YamlMember(typeof(Dictionary), Alias = "handleExitCodes")] [CanBeNull] public Dictionary HandleExitCodes { get; set; } = null; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 5; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; private bool InProgress { get; set; } = false; public void ResetProgress() => InProgress = false; private bool HasExited { get; set; } = false; public int ExitCode { get; set; } public string ErrorString() => String.IsNullOrEmpty(Arguments) ? $"RunAction failed to execute '{Exe}'." : $"RunAction failed to execute '{Exe}' with arguments '{Arguments}'."; public static bool ExistsInPath(string fileName) { if (File.Exists(fileName)) return true; var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) { var fullPath = Path.Combine(path, fileName); if (File.Exists(fullPath)) return true; if (!fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase) && File.Exists(fullPath + ".exe")) return true; } return false; } public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } return HasExited || !Wait ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } public Task RunTask(Output.OutputWriter output) { return null; } private void RunAsProcess(string file, Output.OutputWriter output) { var startInfo = new AugmentedProcess.ProcessStartInfo { CreateNoWindow = !this.CreateWindow, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = ShowError, RedirectStandardOutput = ShowOutput, FileName = file, UseSession0 = HideWindow }; if (Arguments != null) startInfo.Arguments = Environment.ExpandEnvironmentVariables(Arguments); if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } using var exeProcess = new AugmentedProcess.Process { StartInfo = startInfo, EnableRaisingEvents = true }; using (var handler = new OutputHandler("Process", exeProcess, output)) { handler.StartProcess(); if (!Wait) { return; } if (Timeout.HasValue) { var exited = exeProcess.WaitForExit(Timeout.Value); if (!exited) { handler.CancelReading(); exeProcess.Kill(); throw new TimeoutException($"Executable run timeout exceeded."); } } else { bool exited = exeProcess.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(exeProcess.ProcessName, exeProcess.Id)) { exited = exeProcess.WaitForExit(30000); } } } int exitCode = Wrap.ExecuteSafe(() => exeProcess.ExitCode, true, output.LogOptions).Value; if (exitCode != 0) output.WriteLineSafe("Info", $"Process exited with a non-zero exit code: {exitCode}"); HasExited = true; ExitCode = exitCode; if (HandleExitCodes != null) { foreach (string key in HandleExitCodes.Keys) { if (IsApplicableNumber(key, exitCode)) { throw new ErrorHandlingException(HandleExitCodes[key], $"Process '{Exe}' exit code {exitCode} handled with filter '{key}' --> {HandleExitCodes[key]}."); } } } } private void RunAsPrivilegedProcess(string file, Output.OutputWriter output) { var startInfo = new AugmentedProcess.ProcessStartInfo { CreateNoWindow = !this.CreateWindow, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = true, RedirectStandardOutput = true, FileName = file, UseSession0 = HideWindow }; if (Arguments != null) startInfo.Arguments = Arguments; if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; if (!Wait) { startInfo.RedirectStandardError = false; startInfo.RedirectStandardOutput = false; startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.UseShellExecute = true; } if (!ShowOutput) startInfo.RedirectStandardOutput = false; if (!ShowError) startInfo.RedirectStandardError = false; using var exeProcess = new AugmentedProcess.Process { StartInfo = startInfo, EnableRaisingEvents = true }; using (var handler = new OutputHandler("Process", exeProcess, output)) { handler.StartProcess(RunAs); if (!Wait) { return; } if (Timeout.HasValue) { var exited = exeProcess.WaitForExit(Timeout.Value); if (!exited) { handler.CancelReading(); exeProcess.Kill(); throw new TimeoutException($"Executable run timeout exceeded."); } } else { bool exited = exeProcess.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(exeProcess.ProcessName, exeProcess.Id)) { exited = exeProcess.WaitForExit(30000); } } } int exitCode = Wrap.ExecuteSafe(() => exeProcess.ExitCode, true, output.LogOptions).Value; if (exitCode != 0) output.WriteLineSafe("Info", $"Process exited with a non-zero exit code: {exeProcess.ExitCode}"); HasExited = true; ExitCode = exitCode; if (HandleExitCodes != null) { foreach (string key in HandleExitCodes.Keys) { if (IsApplicableNumber(key, exitCode)) { throw new ErrorHandlingException(HandleExitCodes[key], $"Process '{Exe}' exit code {exitCode} handled with filter '{key}' --> {HandleExitCodes[key]}."); } } } } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs ================================================ using System; using System.Collections.Generic; using System.DirectoryServices.ActiveDirectory; using System.Linq; using System.Text; using System.Threading.Tasks; using Core; using Microsoft.Win32; using Microsoft.Win32.TaskScheduler; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { internal enum ScheduledTaskOperation { Delete = 0, Enable = 1, Disable = 2, DeleteFolder = 3 } internal class ScheduledTaskAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(ScheduledTaskOperation), Alias = "operation")] public ScheduledTaskOperation Operation { get; set; } = ScheduledTaskOperation.Delete; [YamlMember(Alias = "data")] public string? RawTask { get; set; } = null; [YamlMember(Alias = "path")] public string Path { get; set; } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Log; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } = false; public void ResetProgress() => InProgress = false; public string ErrorString() => $"ScheduledTaskAction failed to change task {Path} to state {Operation.ToString()}"; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } if (AmeliorationUtil.ISO && Operation != ScheduledTaskOperation.Delete && Operation != ScheduledTaskOperation.DeleteFolder) { output.WriteLineSafe("Warning", "Enabling and disabling scheduled tasks is not supported on ISOs, skipping..."); return UninstallTaskStatus.Completed; } else { using var schedule = (AmeliorationUtil.ISO ? Registry.Users.OpenSubKey($@"HKLM-SOFTWARE-{AmeliorationUtil.ISOGuid}\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache") : Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache"))!; using var taskKey = schedule.OpenSubKey(@"Tree\" + Path.TrimStart('\\').Replace('/', '\\'), true); return (taskKey == null || (!AmeliorationUtil.ISO && Wrap.ExecuteSafe(() => { using TaskService ts = new TaskService(); if (Operation != ScheduledTaskOperation.DeleteFolder) { var task = ts.GetTask(Path); if (task is null) { return Operation == ScheduledTaskOperation.Delete; } if (task.Enabled) { return Operation == ScheduledTaskOperation.Enable; } return Operation == ScheduledTaskOperation.Disable; } else { var folder = ts.GetFolder(Path); if (folder == null) return true; return !folder.GetTasks().Any(); } }, false, true).Value)) ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } } public async Task RunTask(Output.OutputWriter output) { if (GetStatus(output) == UninstallTaskStatus.Completed) { return true; } if (InProgress) throw new TaskInProgressException("Another ScheduledTask action was called while one was in progress."); output.WriteLineSafe("Info", $"{Operation.ToString().TrimEnd('e')}ing scheduled task '{Path}'..."); InProgress = true; if (AmeliorationUtil.ISO) { if (Operation != ScheduledTaskOperation.Delete && Operation != ScheduledTaskOperation.DeleteFolder) output.WriteLineSafe("Warning", "Enabling and disabling scheduled tasks is not supported on ISOs, skipping..."); else DeleteUsingRegistry(Path.TrimStart('\\').Replace('/', '\\')); InProgress = false; return true; } using TaskService ts = new TaskService(); if (Operation != ScheduledTaskOperation.DeleteFolder) { var task = ts.GetTask(Path); if (task is null) { if (Operation == ScheduledTaskOperation.Delete) { return true; } if (RawTask is null || RawTask.Length == 0) { return false; } } switch (Operation) { case ScheduledTaskOperation.Delete: // TODO: This will probably not work if we actually use sub-folders try { ts.RootFolder.DeleteTask(Path, false); DeleteUsingRegistry(Path.TrimStart('\\').Replace('/', '\\')); } catch (Exception e) { Log.EnqueueExceptionSafe(LogType.Warning, e); DeleteUsingRegistry(Path.TrimStart('\\').Replace('/', '\\')); } break; case ScheduledTaskOperation.Enable: case ScheduledTaskOperation.Disable: { if (AmeliorationUtil.ISO) { output.WriteLineSafe("Warning", "Enabling and disabling scheduled tasks is not supported on ISOs, skipping..."); return true; } if (task is null && !(RawTask is null)) { task = ts.RootFolder.RegisterTask(Path, RawTask); } if (!(task is null)) { task.Enabled = Operation == ScheduledTaskOperation.Enable; } else { throw new ArgumentException($"Task provided is null."); } break; } default: throw new ArgumentException($"Argument out of range."); } InProgress = false; return true; } else { if (AmeliorationUtil.ISO) DeleteUsingRegistry(Path); else { var folder = ts.GetFolder(Path); if (folder is null) return true; folder.GetTasks().ToList().ForEach(x => { try { folder.DeleteTask(x.Name, false); DeleteUsingRegistry(System.IO.Path.Combine(Path.TrimStart('\\').Replace('/', '\\'), x.Name)); } catch (Exception e) { Log.EnqueueExceptionSafe(LogType.Warning, e); DeleteUsingRegistry(System.IO.Path.Combine(Path.TrimStart('\\').Replace('/', '\\'), x.Name)); } }); try { folder.Parent.DeleteFolder(folder.Name); DeleteUsingRegistry(Path.TrimStart('\\').Replace('/', '\\')); } catch (Exception e) { Log.EnqueueExceptionSafe(LogType.Warning, e); DeleteUsingRegistry(Path.TrimStart('\\').Replace('/', '\\')); } } InProgress = false; return true; } } private static void DeleteUsingRegistry(string path, bool throwOnTaskNotFound = false) { using var schedule = (AmeliorationUtil.ISO ? Registry.Users.OpenSubKey($@"HKLM-SOFTWARE-{AmeliorationUtil.ISOGuid}\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache") : Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache"))!; using var taskKey = schedule.OpenSubKey(@"Tree\" + path, true); if (taskKey == null) { if (throwOnTaskNotFound) throw new Exception($"Task '{path}' not found in tree."); else return; } foreach (string subTask in taskKey.GetSubKeyNames()) { using var subTaskKey = taskKey.OpenSubKey(subTask)!; if (subTaskKey.GetSubKeyNames().Any()) { subTaskKey.Dispose(); DeleteUsingRegistry(path + "\\" + subTask); } } foreach (string subTask in taskKey.GetSubKeyNames()) { using var subTaskKey = taskKey.OpenSubKey(subTask)!; var id = (string)subTaskKey.GetValue("Id"); if (id != null) { DeleteTaskIdRelations(schedule, id); } subTaskKey.Dispose(); taskKey.DeleteSubKeyTree(subTask); } taskKey.Dispose(); using var treeKey = schedule.OpenSubKey(@"Tree", true); treeKey!.DeleteSubKeyTree(path); } private static void DeleteTaskIdRelations(RegistryKey schedule, string id) { foreach (var relation in new [] { "Boot", "Logon", "Maintenance", "Plain", "Tasks" }) { using var relationKey = schedule.OpenSubKey(relation, true); if (relationKey == null) continue; var match = relationKey.GetSubKeyNames().FirstOrDefault(x => x.Equals(id, StringComparison.OrdinalIgnoreCase)); if (match == null) continue; relationKey.DeleteSubKeyTree(match); } using var flagsKey = schedule.OpenSubKey("TaskStateFlags", true); if (flagsKey == null) return; var flagMatch = flagsKey.GetSubKeyNames().FirstOrDefault(x => x.Equals("/" + id.Replace("\\", "/"), StringComparison.OrdinalIgnoreCase)); if (flagMatch == null) return; flagsKey.DeleteSubKeyTree(flagMatch); } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/ServiceAction.cs ================================================ #nullable enable using System; using System.Collections.Specialized; using System.Configuration.Install; using System.Diagnostics; using System.IO; using System.Linq; using System.Management; using System.ServiceProcess; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; using Core; namespace TrustedUninstaller.Shared.Actions { public enum ServiceOperation { Stop, Continue, Start, Pause, Delete, Change } public class ServiceAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(ServiceOperation), Alias = "operation")] public ServiceOperation Operation { get; set; } = ServiceOperation.Delete; [YamlMember(typeof(string), Alias = "name")] public string ServiceName { get; set; } = null!; [YamlMember(typeof(int), Alias = "startup")] public int? Startup { get; set; } [YamlMember(typeof(bool), Alias = "deleteStop")] public bool DeleteStop { get; set; } = true; [YamlMember(typeof(bool), Alias = "deleteUsingRegistry")] public bool RegistryDelete { get; set; } = false; [YamlMember(typeof(string), Alias = "device")] public bool Device { get; set; } = false; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 4; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => (Operation == ServiceOperation.Stop || Operation == ServiceOperation.Start) ? Tasks.ErrorAction.Log : Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"ServiceAction failed to {Operation.ToString().ToLower()} service {ServiceName}."; private ServiceController? GetService() { if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0); if (ServiceName.EndsWith("*")) return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)); if (ServiceName.StartsWith("*")) return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)); return ServiceController.GetServices() .FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase)); } private ServiceController? GetDevice() { if (ServiceName.EndsWith("*") && ServiceName.StartsWith("*")) return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.IndexOf(ServiceName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0); if (ServiceName.EndsWith("*")) return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.StartsWith(ServiceName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)); if (ServiceName.StartsWith("*")) return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.EndsWith(ServiceName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)); return ServiceController.GetDevices() .FirstOrDefault(service => service.ServiceName.Equals(ServiceName, StringComparison.CurrentCultureIgnoreCase)); } public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) return UninstallTaskStatus.InProgress; if (Operation == ServiceOperation.Change && Startup.HasValue) { // TODO: Implement dev log. Example: // if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry."); using var root = !AmeliorationUtil.ISO ? Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") : Registry.Users.OpenSubKey("HKLM-" + AmeliorationUtil.ISOGuid + $@"\SYSTEM\ControlSet001\Services\{ServiceName}"); if (root == null) return UninstallTaskStatus.Completed; var value = root.GetValue("Start"); return (int)value == Startup.Value ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } if (Operation == ServiceOperation.Delete && (RegistryDelete || AmeliorationUtil.ISO)) { // TODO: Implement dev log. Example: // if (Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") == null) WriteToDevLog($"Warning: Service name '{ServiceName}' not found in registry."); using var root = !AmeliorationUtil.ISO ? Registry.LocalMachine.OpenSubKey($@"SYSTEM\CurrentControlSet\Services\{ServiceName}") : Registry.Users.OpenSubKey("HKLM-" + AmeliorationUtil.ISOGuid + $@"\SYSTEM\ControlSet001\Services\{ServiceName}"); return root == null ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } if (AmeliorationUtil.ISO) return UninstallTaskStatus.Completed; ServiceController? serviceController; if (Device) serviceController = GetDevice(); else serviceController = GetService(); return Operation switch { ServiceOperation.Stop => serviceController == null || serviceController?.Status == ServiceControllerStatus.Stopped || serviceController?.Status == ServiceControllerStatus.StopPending ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Continue => serviceController == null || serviceController?.Status == ServiceControllerStatus.Running || serviceController?.Status == ServiceControllerStatus.ContinuePending ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Start => serviceController?.Status == ServiceControllerStatus.StartPending || serviceController?.Status == ServiceControllerStatus.Running ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Pause => serviceController == null || serviceController?.Status == ServiceControllerStatus.Paused || serviceController?.Status == ServiceControllerStatus.PausePending ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, ServiceOperation.Delete => serviceController == null || Win32.ServiceEx.IsPendingDeleteOrDeleted(serviceController.ServiceName) ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo, _ => throw new ArgumentOutOfRangeException("Argument out of Range", new ArgumentOutOfRangeException()) }; } private readonly string[] RegexNoKill = { "DcomLaunch" }; public override string? IsISOCompatible() => Operation != ServiceOperation.Delete && Operation != ServiceOperation.Change ? "ServiceAction only supports Delete and Change operations with iso." : null; public async Task RunTask(Output.OutputWriter output) { if (InProgress) throw new TaskInProgressException("Another Service action was called while one was in progress."); if (Operation == ServiceOperation.Change && !Startup.HasValue) throw new ArgumentException("Startup property must be specified with the change operation."); if (Operation == ServiceOperation.Change && (Startup.Value > 4 || Startup.Value < 0)) throw new ArgumentException("Startup property must be between 1 and 4."); // This is a little cursed but it works and is concise lol output.WriteLineSafe("Info", $"{Operation.ToString().Replace("Stop", "Stopp").TrimEnd('e')}ing services matching '{ServiceName}'..."); if (Operation == ServiceOperation.Change) { var action = new RegistryValueAction() { KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}", Value = "Start", Data = Startup.Value, Type = RegistryValueType.REG_DWORD, Operation = RegistryValueOperation.Set }; await action.RunTask(output); InProgress = false; return true; } else if (Operation == ServiceOperation.Delete && AmeliorationUtil.ISO) { var action = new RegistryKeyAction() { KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}", Operation = RegistryKeyOperation.Delete }; await action.RunTask(output); InProgress = false; return true; } ServiceController? service; if (Device) service = GetDevice(); else service = GetService(); if (service == null) { output.WriteLineSafe("Info", $"No services found matching '{ServiceName}'."); //Log.WriteSafe(LogType.Warning, $"The service matching '{ServiceName}' does not exist.", new SerializableTrace(), output.LogOptions); if (Operation == ServiceOperation.Start) throw new ArgumentException("Service " + ServiceName + " not found."); return false; } InProgress = true; var cmdAction = new CmdAction(); if ((Operation == ServiceOperation.Delete && DeleteStop) || Operation == ServiceOperation.Stop) { try { foreach (ServiceController dependentService in service.DependentServices.Where(x => x.Status != ServiceControllerStatus.Stopped)) { if (RegexNoKill.Any(regex => Regex.Match(dependentService.ServiceName, regex, RegexOptions.IgnoreCase).Success)) { output.WriteLineSafe("Info", $"Skipping dependent service {dependentService.ServiceName}..."); continue; } output.WriteLineSafe("Info", $"Killing dependent service {dependentService.ServiceName}..."); if (dependentService.Status != ServiceControllerStatus.StopPending && dependentService.Status != ServiceControllerStatus.Stopped) { try { dependentService.Stop(); } catch (Exception e) { dependentService.Refresh(); if (dependentService.Status != ServiceControllerStatus.Stopped && dependentService.Status != ServiceControllerStatus.StopPending) Log.WriteExceptionSafe(LogType.Warning, e, $"Dependent service stop failed.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {dependentService.ServiceName} -caction stop"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); } output.WriteLineSafe("Info", "Waiting for the dependent service to stop..."); try { dependentService.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { dependentService.Refresh(); if (service.Status != ServiceControllerStatus.Stopped) Log.WriteSafe(LogType.Warning, $"Dependent service stop timeout exceeded.", new SerializableTrace(), output.LogOptions); } try { var killServ = new TaskKillAction() { ProcessID = Win32.ServiceEx.GetServiceProcessId(dependentService.ServiceName) }; await killServ.RunTask(output); } catch (Exception e) { dependentService.Refresh(); if (dependentService.Status != ServiceControllerStatus.Stopped) Log.WriteSafe(LogType.Warning, $"Could not kill dependent service {dependentService.ServiceName}.", new SerializableTrace(), output.LogOptions); } } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Error killing dependent services.", output.LogOptions); } } if (Operation == ServiceOperation.Delete) { if (DeleteStop && service.Status != ServiceControllerStatus.StopPending && service.Status != ServiceControllerStatus.Stopped) { if (RegexNoKill.Any(regex => Regex.Match(ServiceName, regex, RegexOptions.IgnoreCase).Success)) { output.WriteLineSafe("Info", $"Skipped stopping critical service {ServiceName}..."); } else { try { service.Stop(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending) Log.WriteExceptionSafe(LogType.Warning, e, $"Service stop failed.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); output.WriteLineSafe("Info", "Waiting for the service to stop..."); try { service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped) Log.WriteSafe(LogType.Warning, $"Service stop timeout exceeded.", new SerializableTrace(), output.LogOptions); } try { var killServ = new TaskKillAction() { ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName) }; await killServ.RunTask(output); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped) Log.WriteSafe(LogType.Warning, $"Could not kill service {service.ServiceName}.", new SerializableTrace(), output.LogOptions); } } } if (RegistryDelete) { var action = new RegistryKeyAction() { KeyName = $@"HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}", Operation = RegistryKeyOperation.Delete }; await action.RunTask(output); } else { try { ServiceInstaller ServiceInstallerObj = new ServiceInstaller(); ServiceInstallerObj.Context = new InstallContext(); ServiceInstallerObj.ServiceName = service.ServiceName; ServiceInstallerObj.Uninstall(null); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Service uninstall failed.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction delete"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); } } else if (Operation == ServiceOperation.Start) { try { service.Start(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running) Log.WriteExceptionSafe(LogType.Warning, e, $"Service start failed.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction start" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction start"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); try { service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running) Log.WriteSafe(LogType.Warning, $"Service start timeout exceeded.", new SerializableTrace(), output.LogOptions); } } else if (Operation == ServiceOperation.Stop) { if (RegexNoKill.Any(regex => Regex.Match(ServiceName, regex, RegexOptions.IgnoreCase).Success)) { output.WriteLineSafe("Info", $"Skipped stopping critical service {ServiceName}..."); return false; } try { service.Stop(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped && service.Status != ServiceControllerStatus.StopPending) Log.WriteExceptionSafe(LogType.Warning, e, $"Service stop failed.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction stop"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); output.WriteLineSafe("Info", "Waiting for the service to stop..."); try { service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped) Log.WriteSafe(LogType.Warning, $"Service stop timeout exceeded.", new SerializableTrace(), output.LogOptions); } try { var killServ = new TaskKillAction() { ProcessID = Win32.ServiceEx.GetServiceProcessId(service.ServiceName) }; await killServ.RunTask(output); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Stopped) Log.WriteSafe(LogType.Warning, $"Could not kill dependent service {service.ServiceName}.", new SerializableTrace(), output.LogOptions); } } else if (Operation == ServiceOperation.Pause) { try { service.Pause(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Paused) Log.WriteExceptionSafe(LogType.Warning, e, $"Service pause failed.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction pause" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction pause"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); try { service.WaitForStatus(ServiceControllerStatus.Paused, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Paused) Log.WriteSafe(LogType.Warning, $"Service pause timeout exceeded.", new SerializableTrace(), output.LogOptions); } } else if (Operation == ServiceOperation.Continue) { try { service.Pause(); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running) Log.WriteExceptionSafe(LogType.Warning, e, $"Service continue failed.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction continue" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction continue"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); try { service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(5000)); } catch (Exception e) { service.Refresh(); if (service.Status != ServiceControllerStatus.Running) Log.WriteSafe(LogType.Warning, $"Service continue timeout exceeded.", new SerializableTrace(), output.LogOptions); } } service?.Dispose(); await Task.Delay(100); InProgress = false; return true; } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/ShortcutAction.cs ================================================ using System; using System.IO; using System.Threading.Tasks; using Core; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; //using IWshRuntimeLibrary; using File = System.IO.File; namespace TrustedUninstaller.Shared.Actions { class ShortcutAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } [YamlMember(typeof(string), Alias = "name")] public string Name { get; set; } [YamlMember(typeof(string), Alias = "destination")] public string Destination { get; set; } [YamlMember(typeof(string), Alias = "description")] public string Description { get; set; } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Log; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"ShortcutAction failed to create shortcut to '{Destination}' from '{RawPath}' with name {Name}."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { //If the shortcut already exists return Completed return File.Exists(Path.Combine(this.Destination, this.Name + ".lnk")) ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } public async Task RunTask(Output.OutputWriter output) { RawPath = Environment.ExpandEnvironmentVariables(RawPath); output.WriteLineSafe("Info", $"Creating shortcut from '{Destination}' to '{RawPath}'..."); if (File.Exists(this.RawPath)) { //WshShell shell = new WshShell(); //var sc = (IWshShortcut)shell.CreateShortcut(Path.Combine(this.Destination, this.Name + ".lnk")); //sc.Description = this.Description; //sc.TargetPath = this.RawPath; //sc.Save(); } else { throw new FileNotFoundException($"File '{RawPath}' not found."); } return true; } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/SoftwareAction.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; using Core; using JetBrains.Annotations; using Microsoft.Win32; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class SoftwareAction : TaskActionWithOutputProcessor, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } public enum SoftwareSource { Chocolatey = 0, } public class SoftwareFallback { [YamlMember(typeof(string), Alias = "name")] public string? Name { get; set; } = null; [YamlMember(typeof(SoftwareSource), Alias = "source")] public SoftwareSource Source { get; set; } = SoftwareSource.Chocolatey; } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 50; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; public void ResetProgress() {} [YamlMember(typeof(ISOSetting), Alias = "iso")] public override ISOSetting ISO { get; set; } = ISOSetting.True; [YamlMember(typeof(OOBESetting?), Alias = "oobe")] public override OOBESetting? OOBE { get; set; } = OOBESetting.True; [YamlMember(typeof(string), Alias = "package")] public string Package { get; set; } = null; [YamlMember(typeof(string), Alias = "name")] public string Name { get; set; } = null; // [YamlMember(typeof(string), Alias = "cache")] // public string Cache { get; set; } = null; [YamlMember(typeof(bool), Alias = "upgrade")] public bool Upgrade { get; set; } = true; [YamlMember(typeof(SoftwareFallback), Alias = "fallback")] public SoftwareFallback Fallback { get; set; } = null; [YamlMember(typeof(SoftwareFallback[]), Alias = "fallbacks")] public SoftwareFallback[] Fallbacks { get; set; } = null; [YamlMember(typeof(SoftwareSource), Alias = "source")] public SoftwareSource Source { get; set; } = SoftwareSource.Chocolatey; public string ErrorString() => $"SoftwareAction failed to install '{Name}'."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { return HasFinished ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; //return GetPackage() == null ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } private bool HasFinished = false; public override string? IsISOCompatible() => ISO == ISOSetting.Only ? "SoftwareAction does not support 'iso: only'. Use 'iso: false' instead to only run it during the OOBE. Otherwise keep the default of 'iso: true' to cache package during ISO mastering, and install it from the cache during OOBE." : null; public async Task RunTask(Output.OutputWriter output) { if (AmeliorationUtil.ISO) { await new WriteStatusAction() { Status = $"Downloading {Name}" }.RunTask(output); } if (AmeliorationUtil.ISO && !Directory.Exists(@$"{AmeliorationUtil.WimPath}\ProgramData\AME\OOBE\Software")) Directory.CreateDirectory(@$"{AmeliorationUtil.WimPath}\ProgramData\AME\OOBE\Software"); /* if (Source == SoftwareSource.Executable) { if (AmeliorationUtil.ISO) { if (!Directory.Exists(@$"{AmeliorationUtil.WimPath}\ProgramData\AME\OOBE\Software")) Directory.CreateDirectory(@$"{AmeliorationUtil.WimPath}\ProgramData\AME\OOBE\Software"); File.Copy(Path.Combine(AmeliorationUtil.Playbook.Path, "Executables", Name), @$"{AmeliorationUtil.WimPath}\ProgramData\AME\OOBE\Software\" + Name); HasFinished = true; return true; } var action = new RunAction() { Exe = Name, Arguments = Arguments, }; action.RunTaskOnMainThread(output); HasFinished = true; return true; } */ if (AmeliorationUtil.ISO) { if (!File.Exists(@$"{AmeliorationUtil.WimPath}\ProgramData\chocolatey\bin\choco.exe")) await InstallISO(output); await InstallToCache(output, Name); HasFinished = true; return true; } if (!File.Exists(Environment.ExpandEnvironmentVariables(@$"%PROGRAMDATA%\chocolatey\bin\choco.exe"))) await Install(output); try { await RunChoco(output, $"install -y --allow-empty-checksums \"{Name}\"{(null == null ? null : $" --source=\"'{Source}'\"")}"); } catch (Exception e) { for (int i = 0; i < 5; i++) { await Task.Delay(1000 * i); try { await RunChoco(output, $"install -y --allow-empty-checksums \"{Name}\""); break; } catch (Exception exception) { Log.EnqueueExceptionSafe(exception); } } } try { if (Upgrade) await RunChoco(output, $"upgrade -y --allow-empty-checksums \"{Name}\""); } catch (Exception e) { Log.EnqueueExceptionSafe(e); } HasFinished = true; return true; } private async Task InstallToCache(Output.OutputWriter output, string name, [CanBeNull] string version = null) { var httpClient = new HttpProgressClient(); httpClient.Client.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); //Required for GitHub var queryString = Uri.EscapeUriString($"((Id eq '{name}') and (not IsPrerelease)) and IsLatestVersion"); var queryUrl = @$"https://community.chocolatey.org/api/v2/Packages?$filter={queryString}"; output.WriteLineSafe("Info", $"Querying '{queryUrl}'..."); string downloadUrl = null; XNamespace ns = "http://www.w3.org/2005/Atom"; try { string xml; for (int i = 0; true; i++) { await Task.Delay(1000 * i); try { var response = await httpClient.GetAsync(queryUrl); response.EnsureSuccessStatusCode(); xml = await response.Content.ReadAsStringAsync(); response.Dispose(); break; } catch (Exception e) { if (i == 3) throw; } } downloadUrl = XDocument.Parse(xml!).Root!.Element(ns + "entry")!.Element(ns + "content")!.Attribute("src")!.Value; } catch (Exception e) { output.WriteLineSafe("Info", $"Package page not found, trying search..."); // In rare cases, the name passed from a dependency does not match the case, so we use search instead which is case insensitive queryUrl = $"https://community.chocolatey.org/api/v2/Search()?$filter=IsLatestVersion&$skip=0&$top=1&searchTerm=%27{Uri.EscapeUriString(name)}%27&targetFramework=%27%27&includePrerelease=false"; output.WriteLineSafe("Info", $"Querying '{queryUrl}'..."); string xml; for (int i = 0; true; i++) { await Task.Delay(1000 * i); try { var response = await httpClient.GetAsync(queryUrl); response.EnsureSuccessStatusCode(); xml = await response.Content.ReadAsStringAsync(); response.Dispose(); break; } catch (Exception ex) { if (i == 3) throw; } } downloadUrl = XDocument.Parse(xml).Root!.Element(ns + "entry")!.Element(ns + "content")!.Attribute("src")!.Value; var realName = downloadUrl.Split('/')[downloadUrl.Split('/').Length - 2]; if (!string.Equals(realName, name, StringComparison.OrdinalIgnoreCase)) throw; name = realName; output.WriteLineSafe("Info", "Package found via search"); } if (version == null) version = downloadUrl.Split('/').Last(); else downloadUrl = downloadUrl.Replace(downloadUrl.Split('/').Last(), version); output.WriteLineSafe("Info", $"Downloading package from {downloadUrl}..."); var downloadDir = System.IO.Path.Combine(AmeliorationUtil.WimPath, "ProgramData\\AME\\OOBE\\Software"); Directory.CreateDirectory(downloadDir); var downloadPath = System.IO.Path.Combine(downloadDir, $"{name}.{version}.nupkg"); if (File.Exists(downloadPath)) return; try { if (!string.IsNullOrEmpty(downloadUrl)) { httpClient.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { if (progressPercentage.HasValue) Console.WriteLine(progressPercentage.Value); }; await httpClient.StartDownload(downloadUrl, downloadPath, 300000); } var extractDir = Path.Combine(downloadDir, Path.GetFileNameWithoutExtension(downloadPath)); Directory.CreateDirectory(extractDir); RunCommand("7za.exe", $"x \"{downloadPath}\" \"{name}.nuspec\" \"tools/*.ps1\" -o\"{extractDir}\" -y"); try { var toolsDir = Path.Combine(extractDir, "tools"); Directory.CreateDirectory(toolsDir); bool installCommandFound = false; string extension = "exe"; foreach (var script in Directory.GetFiles(toolsDir, "*.ps1").Where(x => !x.EndsWith("chocolateyUninstall.ps1") && !x.EndsWith("update.ps1"))) { string[] lines = File.ReadAllLines(script); for (int i = 0; i < lines.Length; i++) { if (lines[i].Contains("Install-ChocolateyPackage")) installCommandFound = true; string pattern = @"^.*filetype[ ]*=[ ]*[""']([a-zA-Z]{3})[""'].*"; var match = Regex.Match(lines[i], pattern, RegexOptions.IgnoreCase); if (match.Success) extension = match.Groups[1].Value; else { pattern = @"^.*Install-ChocolateyPackage[ ]+[^ ]+[ ]+[""']([a-zA-Z]{3})[""'].*"; match = Regex.Match(lines[i], pattern); if (match.Success) extension = match.Groups[1].Value; } } } if (installCommandFound) { List<(string Url, string Path, string Hash)> files = new List<(string Url, string Path, string Hash)>(); bool urlReplaced = false; foreach (var script in Directory.GetFiles(toolsDir, "*.ps1").Where(x => !x.EndsWith("chocolateyUninstall.ps1") && !x.EndsWith("update.ps1"))) { string[] lines = File.ReadAllLines(script); for (int i = 0; i < lines.Length; i++) { string pattern = @"(^.*(url64|64biturl|url64bit|url|url32|32biturl|url32bit)[ ]*=[ ]*)[""']([^'""]*)[""'](.*)"; var match = Regex.Match(lines[i], pattern, RegexOptions.IgnoreCase); if (!match.Success) continue; string prefix = match.Groups[1].Value; string url = match.Groups[3].Value; string suffix = ""; if (match.Groups.Count > 4) suffix = match.Groups[4].Value; url = url.Replace("${locale}", "en-US"); if (url.Contains("${")) { output.WriteLineSafe("Info", $"Unrecognized variable in url '{url}, skipping download"); continue; } var matchingFile = files.FirstOrDefault(x => x.Url == url); string destination = matchingFile.Path ?? Path.Combine(downloadDir, name + "_" + i + "." + extension); if (matchingFile.Path == null) { try { httpClient.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { if (progressPercentage.HasValue) output.WriteLineSafe("Progress", $"Downloaded {StringUtils.HumanReadableBytes((ulong)totalBytesDownloaded)}"); }; var hash = await httpClient.StartDownload(url, destination, 300000); if (files.Any(x => x.Hash == hash)) { try { File.Delete(destination); } catch (Exception e) { } destination = files.First(x => x.Hash == hash).Path; } files.Add((url, destination, hash)); } catch (Exception e) { Log.EnqueueExceptionSafe(LogType.Warning, e); continue; } } urlReplaced = true; string modifiedUrl = destination.Replace($@"{AmeliorationUtil.WimPath}\", @"C:\"); lines[i] = Regex.Replace(lines[i], pattern, _ => { return $"{prefix}'{modifiedUrl}'{suffix}"; }, RegexOptions.IgnoreCase); } File.WriteAllLines(script, lines); } if (urlReplaced) { foreach (var script in Directory.GetFiles(toolsDir, "*.ps1").Where(x => !x.EndsWith("chocolateyUninstall.ps1") && !x.EndsWith("update.ps1"))) { string text = File.ReadAllText(script).Replace("Install-ChocolateyPackage", "Install-ChocolateyPackage -UseOriginalLocation"); File.WriteAllText(script, text); } } RunCommand("7za.exe", $"a \"{downloadPath}\" \"{extractDir}\\*.ps1\" -r -y"); } await InstallDependencies(output, File.ReadAllText(Path.Combine(extractDir, $"{name}.nuspec")), name); } finally { try { Directory.Delete(extractDir, true); } catch (Exception e) { Log.EnqueueExceptionSafe(LogType.Warning, e); } } } catch (Exception e) { try { File.Delete(downloadPath); } catch (Exception exception) { Log.EnqueueExceptionSafe(LogType.Warning, exception); } throw; } try { var imageQuery = Uri.EscapeUriString($"{name}.{version}.svg"); await httpClient.StartDownload($"https://community.chocolatey.org/content/packageimages/{imageQuery}", Path.Combine(downloadDir, $"{name}.svg")); } catch (Exception e) { //Console.WriteLine(e); } httpClient.Dispose(); } private async Task InstallDependencies(Output.OutputWriter output, string xml, string parent) { var dependencies = XDocument.Parse(xml).Root!.Elements().First(x => x.Name.LocalName == "metadata").Elements().FirstOrDefault(x => x.Name.LocalName == "dependencies"); if (dependencies == null) return; foreach (var dep in dependencies.Elements().Where(x => x.Name.LocalName == "dependency")) { string id = (string)dep.Attribute("id"); string version = (string)dep.Attribute("version"); output.WriteLineSafe("Info", $"Installing dependency '{id + (version == null ? "" : "." + version)}' for '{parent}'"); if (version != null && (version.StartsWith("[") && version.EndsWith("]"))) await InstallToCache(output, id, version.Trim('[', ']')); else await InstallToCache(output, id); } } private static async Task RunChoco(Output.OutputWriter output, string arguments) { var startInfo = new ProcessStartInfo { CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = true, RedirectStandardOutput = true, FileName = Environment.ExpandEnvironmentVariables(@$"%PROGRAMDATA%\chocolatey\bin\choco.exe"), Arguments = arguments, }; using var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(); bool exited = process.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(process.ProcessName, process.Id)) { exited = process.WaitForExit(30000); } } if (process.ExitCode != 0) throw new Exception($"Chocolatey exited with code {process.ExitCode}"); } private static async Task InstallISO(Output.OutputWriter output) { string installDir = @$"{AmeliorationUtil.WimPath}\ProgramData\chocolatey"; var userChocoCache = Environment.GetEnvironmentVariable("ChocolateyInstall", EnvironmentVariableTarget.User); var systemChocoCache = Environment.GetEnvironmentVariable("ChocolateyInstall", EnvironmentVariableTarget.Machine); var processChocoCache = Environment.GetEnvironmentVariable("ChocolateyInstall", EnvironmentVariableTarget.Process); try { Environment.SetEnvironmentVariable("ChocolateyInstall", null, EnvironmentVariableTarget.User); Environment.SetEnvironmentVariable("ChocolateyInstall", installDir, EnvironmentVariableTarget.Machine); Environment.SetEnvironmentVariable("ChocolateyInstall", installDir, EnvironmentVariableTarget.Process); await Install(output); foreach (var rootKeyRaw in Registry.Users.GetSubKeyNames().Where(x => x.StartsWith("HKLM-SYSTEM-" + AmeliorationUtil.ISOGuid)).Select(x => Registry.Users.OpenSubKey(x, true))) { using var rootKey = rootKeyRaw; using var key = rootKey!.OpenSubKey(@"ControlSet001\Control\Session Manager\Environment", true); var path = key!.GetValue("Path") as string; key!.SetValue("Path", path!.TrimEnd(';') + @$";C:\ProgramData\chocolatey\bin", RegistryValueKind.String); key!.SetValue("ChocolateyInstall", @"C:\ProgramData\chocolatey", RegistryValueKind.String); } } finally { Environment.SetEnvironmentVariable("ChocolateyInstall", userChocoCache, EnvironmentVariableTarget.User); Environment.SetEnvironmentVariable("ChocolateyInstall", systemChocoCache, EnvironmentVariableTarget.Machine); Environment.SetEnvironmentVariable("ChocolateyInstall", processChocoCache, EnvironmentVariableTarget.Process); string pathToRemove = installDir + "\\bin"; string currentPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); if (currentPath != null) { var paths = currentPath.Split(';') .Select(p => p.Trim()) .Where(p => !string.IsNullOrEmpty(p)) .ToList(); if (paths.Contains(pathToRemove)) { paths.Remove(pathToRemove); string newPath = string.Join(";", paths); Environment.SetEnvironmentVariable("Path", newPath, EnvironmentVariableTarget.Machine); } else { Log.EnqueueSafe(LogType.Warning, "Path not found in system PATH.", null); } } else { Log.EnqueueSafe(LogType.Warning, "Failed to retrieve the system PATH variable.", null); } } } private static async Task Install(Output.OutputWriter output) { var env = Environment.GetEnvironmentVariable("ChocolateyInstall"); if (!string.IsNullOrEmpty(env)) output.WriteLineSafe("Info", $"Installing Chocolatey to '{env}'..."); else output.WriteLineSafe("Info", $"Installing Chocolatey..."); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; using (var httpClient = new HttpProgressClient()) { httpClient.Client.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); var queryString = Uri.EscapeUriString("((Id eq 'chocolatey') and (not IsPrerelease)) and IsLatestVersion"); var queryUrl = @$"https://community.chocolatey.org/api/v2/Packages?$filter={queryString}"; var xml = await Wrap.Retry().Execute(async () => { var response = await httpClient.GetAsync(queryUrl); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); }); string downloadUrl = XDocument.Parse(xml).Root!.Elements().First(x => x.Name.LocalName == "entry").Elements().First(x => x.Name.LocalName == "content").Attribute("src")!.Value; var version = downloadUrl.Split('/').Last(); var downloadPath = Path.GetTempFileName(); if (!string.IsNullOrEmpty(downloadUrl)) { httpClient.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { if (progressPercentage.HasValue) output.WriteLineSafe("Progress", $"Downloaded {StringUtils.HumanReadableBytes((ulong)totalBytesDownloaded)}"); }; await httpClient.StartDownload(downloadUrl, downloadPath, 300000); } var temp = Path.GetTempPath() + "\\AME-CHOCO-" + new Random().Next(10000, 700000); ExtractArchive(downloadPath, temp); var startInfo = new ProcessStartInfo { CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, RedirectStandardError = true, RedirectStandardOutput = true, FileName = "powershell.exe", Arguments = $"-NoP -ExecutionPolicy Bypass -File \"{Path.Combine(temp, "tools\\chocolateyInstall.ps1")}\"", }; using var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; using (var handler = new OutputHandler("Process", process, output)) { handler.StartProcess(); bool exited = process.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(process.ProcessName, process.Id)) { exited = process.WaitForExit(30000); } } try { File.Delete(downloadPath); Directory.Delete(temp, true); } catch (Exception e) { Log.EnqueueExceptionSafe(e); } } } public static void ExtractArchive(string file, string targetDir) { RunCommand("7za.exe", $"x \"{file}\" -o\"{targetDir}\" -y -aos"); } private static void RunCommand(string exe, string command, bool printOutput = false) { var proc = new Process(); var startInfo = new ProcessStartInfo { CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Normal, Arguments = command, FileName = exe, RedirectStandardError = true, RedirectStandardOutput = printOutput }; proc.StartInfo = startInfo; proc.Start(); StringBuilder errorOutput = new StringBuilder(""); proc.ErrorDataReceived += (sender, args) => { errorOutput.Append("\r\n" + args.Data); }; proc.BeginErrorReadLine(); if (printOutput) { proc.OutputDataReceived += (sender, args) => { Console.WriteLine(args.Data); }; proc.BeginOutputReadLine(); } proc.WaitForExit(); proc.CancelErrorRead(); // TODO: //if (proc.ExitCode == 1) // Log.EnqueueSafe(LogType.Error, "Warning while running 7zip: " + errorOutput.ToString(), null, ("Command", command)); if (proc.ExitCode > 1 || (!exe.EndsWith("7za.exe") && proc.ExitCode != 0)) throw new ArgumentOutOfRangeException($"Error running '{exe.Split('\\').Last()}' ({proc.ExitCode}): " + errorOutput.ToString()); } 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.FromMinutes(1) }; } public async Task StartDownload(string downloadUrl, string destinationFilePath, long? size = null) { _downloadUrl = downloadUrl; _destinationFilePath = destinationFilePath; for (int i = 0; i < 10; i++) { await Task.Delay(1000 * i); using (var response = await Client.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) { if (response.StatusCode == HttpStatusCode.ServiceUnavailable) { Log.WriteSafe(LogType.Warning, "Received 503 service outage error, retrying...", null); continue; } if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.NotFound) { Log.WriteSafe(LogType.Warning, $"Received {response.StatusCode} response error, retrying...", null); i = 9; continue; } return await DownloadFileFromHttpResponseMessage(response, size); } } throw new Exception("Unexpected end of StartDownload."); } public Task GetAsync(string link) { return Client.GetAsync(link); } private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response, long? size) { response.EnsureSuccessStatusCode(); if (response.Content.Headers.ContentLength.HasValue && response.Content.Headers.ContentLength.Value != 0) size = response.Content.Headers.ContentLength; using (var contentStream = await response.Content.ReadAsStreamAsync()) return 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 md5 = MD5.Create()) { 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; } md5.TransformBlock(buffer, 0, bytesRead, buffer, 0); await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 50 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } md5.TransformFinalBlock(Array.Empty(), 0, 0); return BitConverter.ToString(md5.Hash).Replace("-", "").ToUpper(); } } private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) { if (ProgressChanged == null) return; double? progressPercentage = null; if (totalDownloadSize.HasValue) { progressPercentage = Math.Min(Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2), 100); } ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); } public void Dispose() { Client?.Dispose(); } } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/SystemPackageAction.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using Core; namespace TrustedUninstaller.Shared.Actions { // Integrate ame-assassin later internal class SystemPackageAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } public enum Architecture { amd64 = 0, wow64 = 1, msil = 2, x86 = 3, All = 4 } [YamlMember(typeof(string), Alias = "name")] public string Name { get; set; } [YamlMember(typeof(string), Alias = "arch")] public Architecture Arch { get; set; } = Architecture.All; [YamlMember(typeof(string), Alias = "language")] public string Language { get; set; } = "*"; [YamlMember(typeof(string), Alias = "regexExcludeFiles")] public string[]? RegexExcludeList { get; set; } [YamlMember(typeof(string), Alias = "excludeDependents")] public string[]? ExcludeDependentsList { get; set; } [YamlMember(typeof(string[]), Alias = "weight")] public int ProgressWeight { get; set; } = 15; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"SystemPackageAction failed to remove '{Name}'."; public override string? IsISOCompatible() => "SystemPackageAction does not support iso."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) return UninstallTaskStatus.InProgress; return HasFinished ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } private bool HasFinished = false; public async Task RunTask(Output.OutputWriter output) { if (InProgress) throw new TaskInProgressException("Another Appx action was called while one was in progress."); InProgress = true; output.WriteLineSafe("Info", $"Removing system package '{Name}'..."); var excludeArgs = new StringBuilder(""); if (RegexExcludeList != null) { foreach (var regex in RegexExcludeList) { excludeArgs.Append(@$" -xf ""{regex}"""); } } var excludeDependsArgs = new StringBuilder(""); if (ExcludeDependentsList != null) { foreach (var dependent in ExcludeDependentsList) { excludeDependsArgs.Append(@$" -xdependent ""{dependent}"""); } } string kernelDriverArg = AmeliorationUtil.UseKernelDriver ? " -UseKernelDriver" : ""; var psi = new ProcessStartInfo() { UseShellExecute = false, CreateNoWindow = true, Arguments = $@"-SystemPackage ""{Name}"" -Arch {Arch.ToString()} -Language ""{Language}""" + excludeArgs + excludeDependsArgs + kernelDriverArg, FileName = Directory.GetCurrentDirectory() + "\\ame-assassin\\ame-assassin.exe", RedirectStandardOutput = true, RedirectStandardError = true }; outputWriter = output; var proc = Process.Start(psi); proc.OutputDataReceived += ProcOutputHandler; proc.ErrorDataReceived += ProcOutputHandler; proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); bool exited = proc.WaitForExit(30000); // WaitForExit alone seems to not be entirely reliable while (!exited && ExeRunning(proc)) { exited = proc.WaitForExit(30000); } HasFinished = true; InProgress = false; return true; } private Output.OutputWriter outputWriter = null; private void ProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) { if (!string.IsNullOrWhiteSpace(outLine.Data)) outputWriter.WriteLineSafe("Process", outLine.Data); } private static bool ExeRunning(Process process) { try { return Process.GetProcessesByName(process.ProcessName).Any(x => x.Id == process.Id); } catch (Exception) { return false; } } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/TaskAction.cs ================================================ using System; using System.Threading.Tasks; using Core; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class TaskAction : Tasks.TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "path")] public string Path { get; set; } [YamlMember(typeof(ISOSetting), Alias = "iso")] public override ISOSetting ISO { get; set; } = ISOSetting.True; [YamlMember(typeof(OOBESetting?), Alias = "oobe")] public override OOBESetting? OOBE { get; set; } = OOBESetting.True; public void RunTaskOnMainThread(Output.OutputWriter output) => throw new NotImplementedException(); private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => ""; public int GetProgressWeight() => 0; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => false; public UninstallTaskStatus GetStatus(Output.OutputWriter output) => throw new NotImplementedException(); public Task RunTask(Output.OutputWriter output) => throw new NotImplementedException(); } } ================================================ FILE: TrustedUninstaller.Shared/Actions/TaskKillAction.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Management; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; using Core; namespace TrustedUninstaller.Shared.Actions { class TaskKillAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, int dwProcessId); public enum ProcessAccessFlags : uint { QueryLimitedInformation = 0x1000 } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); [YamlMember(typeof(string), Alias = "name")] public string? ProcessName { get; set; } [YamlMember(typeof(string), Alias = "pathContains")] public string? PathContains { get; set; } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 2; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Log; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public int? ProcessID { get; set; } public string ErrorString() { string text = $"TaskKillAction failed to kill processes matching '{ProcessName}'."; try { var processes = GetProcess().Select(process => process.ProcessName).Distinct().ToList(); if (processes.Count > 1) { text = $"TaskKillAction failed to kill processes:"; foreach (var process in processes) { text += "|NEWLINE|" + process; } } else if (processes.Count == 1) text = $"TaskKillAction failed to kill process {processes[0]}."; } catch (Exception) { } return text; } public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } List processToTerminate = new List(); if (ProcessID.HasValue) { try { processToTerminate.Add(Process.GetProcessById((int)ProcessID)); } catch (Exception) { } } else { processToTerminate = GetProcess().ToList(); } return processToTerminate.Any() ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed; } private List GetProcess() { if (ProcessID.HasValue) { var list = new List(); try { var process = Process.GetProcessById(ProcessID.Value); if (ProcessName == null || process.ProcessName.Equals(ProcessName, StringComparison.OrdinalIgnoreCase)) list.Add(process); else return list; } catch (Exception e) { return list; } } if (ProcessName == null) { return new List(); } if (ProcessName.EndsWith("*") && ProcessName.StartsWith("*")) return Process.GetProcesses().ToList() .Where(process => process.ProcessName.IndexOf(ProcessName.Trim('*'), StringComparison.CurrentCultureIgnoreCase) >= 0).ToList(); if (ProcessName.EndsWith("*")) return Process.GetProcesses() .Where(process => process.ProcessName.StartsWith(ProcessName.TrimEnd('*'), StringComparison.CurrentCultureIgnoreCase)).ToList(); if (ProcessName.StartsWith("*")) return Process.GetProcesses() .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)).ToList(); return Process.GetProcessesByName(ProcessName).ToList(); } [DllImport("kernel32.dll", SetLastError=true)] static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical); private readonly string[] RegexNoKill = { "lsass", "csrss", "winlogon", "TrustedUninstaller\\.CLI", "dwm", "conhost", "ame.?wizard", "ame.?assassin" }; // These processes give access denied errors when getting their handle for IsProcessCritical. // TODO: Investigate how to properly acquire permissions. private readonly string[] RegexNotCritical = { "SecurityHealthService", "wscsvc", "MsMpEng", "SgrmBroker" }; public override string? IsISOCompatible() => "TaskKillAction does not support iso."; public async Task RunTask(Output.OutputWriter output) { InProgress = true; if (string.IsNullOrEmpty(ProcessName) && ProcessID.HasValue) { output.WriteLineSafe("Info", $"Killing process with PID '{ProcessID.Value}'..."); } else { if (ProcessName != null && RegexNoKill.Any(regex => Regex.Match(ProcessName, regex, RegexOptions.IgnoreCase).Success)) { output.WriteLineSafe("Info", $"Skipping {ProcessName}..."); return false; } output.WriteLineSafe("Info", $"Killing processes matching '{ProcessName}'..."); } var cmdAction = new CmdAction(); if (ProcessName != null) { //If the service is svchost, we stop the service instead of killing it. if (ProcessName.Equals("svchost", StringComparison.OrdinalIgnoreCase)) { try { if (ProcessID.HasValue) { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(ProcessID.Value)) { try { var stopServ = new ServiceAction() { ServiceName = serviceName, Operation = ServiceOperation.Stop }; await stopServ.RunTask(output); } catch (Exception e) { output.WriteLineSafe("Info", $"Could not kill service " + serviceName + ": " + e.Message); Log.WriteExceptionSafe(LogType.Warning, e, $"Could not kill service.", output.LogOptions); } } } else { foreach (var process in GetProcess()) { foreach (var serviceName in Win32.ServiceEx.GetServicesFromProcessId(process.Id)) { try { var stopServ = new ServiceAction() { ServiceName = serviceName, Operation = ServiceOperation.Stop }; await stopServ.RunTask(output); } catch (Exception e) { output.WriteLineSafe("Info", $"Could not kill service " + serviceName + ": " + e.Message); Log.WriteExceptionSafe(LogType.Warning, e, $"Could not kill service", output.LogOptions); } } } } } catch (NullReferenceException e) { output.WriteLineSafe("Info", $"A service with PID: {ProcessID.Value} could not be found."); Log.WriteExceptionSafe(LogType.Warning, e, $"Could not find service with PID {ProcessID.Value}.", output.LogOptions); } int i; for (i = 0; i <= 6 && GetProcess().Any(); i++) { await Task.Delay(100 * i); } if (i < 6) { InProgress = false; return true; } } if (PathContains != null && !ProcessID.HasValue) { var processes = GetProcess(); if (processes.Count > 0) output.WriteLineSafe("Info", "Processes:"); foreach (var process in processes.Where(x => { try { return x.MainModule.FileName.Contains(PathContains); } catch (Exception e) { return false; } })) { output.WriteLineSafe("Info", process.ProcessName + " - " + process.Id); if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); IsProcessCritical(hprocess, ref isCritical); CloseHandle(hprocess); if (isCritical) { output.WriteLineSafe("Info", $"{process.ProcessName} is a critical process, skipping..."); continue; } } try { if (!process.HasExited && !TerminateProcess(process.Handle, 1)) Log.WriteExceptionSafe(LogType.Warning, new Win32Exception(), $"TerminateProcess failed with error code.", output.LogOptions); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Could not open process handle.", output.LogOptions); } try { process.WaitForExit(1000); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Error waiting for process exit.", output.LogOptions); } if (process.ProcessName == "explorer") continue; cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate"; if (AmeliorationUtil.UseKernelDriver && process.ProcessName != "explorer") cmdAction.RunTaskOnMainThread(output); int i = 0; while (i <= 3 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { try { try { if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); else TerminateProcess(process.Handle, 1); } catch (Exception e) { } process.WaitForExit(500); } catch (Exception e) { } await Task.Delay(100); i++; } if (i >= 3) Log.WriteSafe(LogType.Warning, $"Task kill timeout exceeded.", new SerializableTrace(), output.LogOptions); } InProgress = false; return true; } } if (ProcessID.HasValue) { var process = Process.GetProcessById(ProcessID.Value); if (ProcessName != null && ProcessName.Equals("explorer", StringComparison.OrdinalIgnoreCase)) { try { if (!process.HasExited && !TerminateProcess(process.Handle, 1)) Log.WriteExceptionSafe(LogType.Warning, new Win32Exception(), $"TerminateProcess failed with error code.", output.LogOptions); try { process.WaitForExit(1000); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Error waiting for process exit.", output.LogOptions); } } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Could not open process handle.", output.LogOptions); } } else { if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; try { IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); IsProcessCritical(hprocess, ref isCritical); CloseHandle(hprocess); } catch (InvalidOperationException e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Could not check if process is critical.", output.LogOptions); return false; } if (isCritical) { output.WriteLineSafe("Info", $"{process.ProcessName} is a critical process, skipping..."); return false; } } try { if (!process.HasExited && !TerminateProcess(process.Handle, 1)) Log.WriteExceptionSafe(LogType.Warning, new Win32Exception(), $"TerminateProcess failed with error code.", output.LogOptions); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Could not open process handle.", output.LogOptions); } try { process.WaitForExit(1000); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Error waiting for process exit.", output.LogOptions); } cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID.Value} -caction terminate" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {ProcessID.Value} -caction terminate"; if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); } int i = 0; while (i <= 3 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { try { try { if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); else TerminateProcess(process.Handle, 1); } catch (Exception e) { } process.WaitForExit(500); } catch (Exception e) { } await Task.Delay(100); i++; } if (i >= 3) Log.WriteSafe(LogType.Warning, $"Task kill timeout exceeded.", new SerializableTrace(), output.LogOptions); } else { var processes = GetProcess(); if (processes.Count > 0) output.WriteLineSafe("Info", "Processes:"); foreach (var process in processes) { output.WriteLineSafe("Info", process.ProcessName + " - " + process.Id); if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; try { IntPtr hprocess = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, process.Id); IsProcessCritical(hprocess, ref isCritical); CloseHandle(hprocess); } catch (InvalidOperationException e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Could not check if process is critical.", output.LogOptions); continue; } if (isCritical) { output.WriteLineSafe("Info", $"{process.ProcessName} is a critical process, skipping..."); continue; } } try { if (!process.HasExited && !TerminateProcess(process.Handle, 1)) Log.WriteExceptionSafe(LogType.Warning, new Win32Exception(), $"TerminateProcess failed with error code.", output.LogOptions); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Could not open process handle.", output.LogOptions); } try { process.WaitForExit(1000); } catch (Exception e) { Log.WriteExceptionSafe(LogType.Warning, e, $"Error waiting for process exit.", output.LogOptions); } if (process.ProcessName == "explorer") continue; cmdAction.Command = Environment.Is64BitOperatingSystem ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate" : $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype process -cobject {process.Id} -caction terminate"; if (AmeliorationUtil.UseKernelDriver && process.ProcessName != "explorer") cmdAction.RunTaskOnMainThread(output); int i = 0; while (i <= 3 && GetProcess().Any(x => x.Id == process.Id && x.ProcessName == process.ProcessName)) { try { try { if (AmeliorationUtil.UseKernelDriver) cmdAction.RunTaskOnMainThread(output); else TerminateProcess(process.Handle, 1); } catch (Exception e) { } process.WaitForExit(500); } catch (Exception e) { } await Task.Delay(100); i++; } if (i >= 3) Log.WriteSafe(LogType.Warning, $"Task kill timeout exceeded.", new SerializableTrace(), output.LogOptions); } } InProgress = false; return true; } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/UpdateAction.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Core; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { internal class UpdateAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "name")] public string PackageName { get; set; } [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"UpdateAction failed to remove update package {PackageName}."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } return UninstallTaskStatus.Completed; } public async Task RunTask(Output.OutputWriter output) { if (InProgress) { output.WriteLineSafe("Info", "An update action is already in progress..."); return false; } InProgress = true; output.WriteLineSafe("Info", $"Removing update package '{PackageName}'..."); CmdAction removeUpdate = new CmdAction() { Command = @$"DISM.exe /Online /Remove-Package /PackageName:{PackageName} /quiet /norestart" }; while(removeUpdate.GetStatus(output) != UninstallTaskStatus.Completed) { await removeUpdate.RunTask(Output.OutputWriter.Null); } InProgress = false; return true; } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/UserAction.cs ================================================ using System; using System.DirectoryServices; using System.Threading.Tasks; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; using System.DirectoryServices.AccountManagement; using System.Security.Principal; using Core; namespace TrustedUninstaller.Shared.Actions { public class UserAction : Tasks.TaskAction, ITaskAction { public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "name")] public string Username { get; set; } = ""; [YamlMember(typeof(bool), Alias = "admin")] public bool IsAdmin { get; set; } = false; [YamlMember(typeof(string), Alias = "weight")] public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Notify; public bool GetRetryAllowed() => true; private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => $"UserAction failed to change permissions for user {Username}."; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { using var pc = new PrincipalContext(ContextType.Machine); var up = UserPrincipal.FindByIdentity( pc, IdentityType.SamAccountName, this.Username); var userExists = (up != null); if (!IsAdmin || !userExists) return userExists ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; var identity = new WindowsIdentity(up.UserPrincipalName); var principal = new WindowsPrincipal(identity); var isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator); return isAdmin ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } public async Task RunTask(Output.OutputWriter output) { if (this.GetStatus(output) != UninstallTaskStatus.ToDo) { return false; } output.WriteLineSafe("Info", $"Changing permissions for user '{Username}'..."); return await Task.Run(() => { using var pc = new PrincipalContext(ContextType.Machine); var up = UserPrincipal.FindByIdentity( pc, IdentityType.SamAccountName, this.Username); var userExists = (up != null); var ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer"); if (!userExists) { var newUser = ad.Children.Add(this.Username, "user"); newUser.Invoke("SetPassword", "user"); newUser.Invoke("Put", "Description", "Created by the AME"); newUser.CommitChanges(); if (IsAdmin) { var group = ad.Children.Find("Administrators", "group"); group.Invoke("Add", newUser.Path); group.CommitChanges(); } } else { if (IsAdmin) { var group = ad.Children.Find("Administrators", "group"); group.Invoke("Add", up.UserPrincipalName); group.CommitChanges(); } } return true; }); } } } ================================================ FILE: TrustedUninstaller.Shared/Actions/WriteStatusAction.cs ================================================ using System; using System.IO; using System.Threading.Tasks; using Core; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { public class WriteStatusAction : Tasks.TaskAction, ITaskAction { public static IProgress StatusReporter { get; set; } = null; public void RunTaskOnMainThread(Output.OutputWriter output) { throw new NotImplementedException(); } [YamlMember(typeof(string), Alias = "status")] public string Status { get; set; } private bool InProgress { get; set; } public void ResetProgress() => InProgress = false; public string ErrorString() => ""; public int GetProgressWeight() => 0; public ErrorAction GetDefaultErrorAction() => Tasks.ErrorAction.Log; public bool GetRetryAllowed() => false; public UninstallTaskStatus GetStatus(Output.OutputWriter output) { if (InProgress) { return UninstallTaskStatus.InProgress; } return hasCompleted ? UninstallTaskStatus.Completed : UninstallTaskStatus.ToDo; } private bool hasCompleted; public async Task RunTask(Output.OutputWriter output) { hasCompleted = true; const string separator = "--------------------------------------------------"; output.WriteLineRawSafe((File.Exists(output.OutputFile) ? Environment.NewLine + separator : separator) + Environment.NewLine + $"[Status] {Status}" + Environment.NewLine + separator + Environment.NewLine); if (StatusReporter == null) return true; if (String.IsNullOrWhiteSpace(Status)) StatusReporter.Report("Running actions"); else StatusReporter.Report(Status); return true; } } } ================================================ FILE: TrustedUninstaller.Shared/AmeliorationUtil.cs ================================================ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.ServiceProcess; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Xml; using System.Xml.Serialization; using Core; using Core.Actions; using Core.Exceptions; using Interprocess; using iso_mode; using JetBrains.Annotations; using ManagedWimLib; using Microsoft.Wim; using Microsoft.Win32; using TrustedUninstaller.Shared.Actions; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Parser; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Core; using YamlDotNet.Serialization; using RegistryKeyAction = TrustedUninstaller.Shared.Actions.RegistryKeyAction; using RegistryValueAction = Core.Actions.RegistryValueAction; using RegistryValueType = TrustedUninstaller.Shared.Actions.RegistryValueType; using RunAction = TrustedUninstaller.Shared.Actions.RunAction; using TaskAction = TrustedUninstaller.Shared.Tasks.TaskAction; using UninstallTaskStatus = TrustedUninstaller.Shared.Tasks.UninstallTaskStatus; using Win32 = Core.Win32; namespace TrustedUninstaller.Shared { public static class AmeliorationUtil { private static readonly HttpClient Client = new HttpClient(); public static Playbook Playbook { set; get; } = new Playbook(); public static bool UseKernelDriver = false; public static readonly List ErrorDisplayList = new List(); public static int GetProgressMaximum(List actions) => actions.Sum(action => action.GetProgressWeight()); public static bool LiveISO = false; public static bool ISO = false; public static string ISOGuid; public static string WimPath; public static WimWrapper WimInstance; private static bool IsApplicable([CanBeNull] Playbook upgradingFrom, bool? onUpgrade, [CanBeNull] string[] onUpgradeVersions, [CanBeNull] string option) { if (upgradingFrom == null) return !onUpgrade.GetValueOrDefault(); bool isApplicable = true; bool? versionApplicable = null; if (onUpgradeVersions != null) { if (onUpgrade == null) throw new YamlException("onUpgrade must be defined when using onUpgradeVersions"); versionApplicable = onUpgradeVersions.Where(version => !version.StartsWith("!")).Any(version => IsApplicableUpgrade(upgradingFrom.Version, version)) && onUpgradeVersions.Where(version => version.StartsWith("!")).All(version => IsApplicableUpgrade(upgradingFrom.Version, version)); isApplicable = versionApplicable.Value; } if (onUpgrade == null) return true; if (isApplicable && option != null) { isApplicable = String.Equals(option.Trim(), "ignore", StringComparison.OrdinalIgnoreCase) || (!(upgradingFrom.AvailableOptions?.Contains(option) ?? false) || (upgradingFrom.SelectedOptions?.Contains(option) ?? false)); } if (upgradingFrom.GetVersionNumber() == Playbook.GetVersionNumber() && (versionApplicable == null || !onUpgradeVersions.Any(x => x.Equals(Playbook.Version)))) return !onUpgrade.Value && !isApplicable; return onUpgrade.Value ? isApplicable : !isApplicable; } [CanBeNull] public static List ParseActions(string configPath, [CanBeNull] string isoBuild, [CanBeNull] string isoUpdateBuild, Architecture? isoArch, List options, string file, [CanBeNull] Playbook upgradingFrom) { var returnExceptionMessage = string.Empty; try { if (!File.Exists(Path.Combine(configPath, file))) return null; var configData = File.ReadAllText(Path.Combine(configPath, file)); var task = PlaybookParser.Deserializer.Deserialize(configData); //if (task.ISO == ISOSetting.Only && task.OOBE == OOBESetting.Only) // throw new SerializationException($"Cannot have both ISO and OOBE set to only on task."); if ((!IsApplicable(upgradingFrom, task.OnUpgrade, task.OnUpgradeVersions, task.PreviousOption ?? task.Option) || !IsApplicableOption(task.Option, Playbook.Options) || !IsApplicableArch(task.Arch, ISO ? isoArch?.ToString() : null)) || (task.Builds != null && ( !task.Builds.Where(build => !build.StartsWith("!")).Any(build => IsApplicableWindowsVersion(build, ISO, isoBuild, isoUpdateBuild)) || task.Builds.Where(build => build.StartsWith("!")).Any(build => !IsApplicableWindowsVersion(build, ISO, isoBuild, isoUpdateBuild)))) || (task.Options != null && ( !task.Options.Where(option => !option.StartsWith("!")).Any(option => IsApplicableOption(option, Playbook.Options)) || task.Options.Where(option => option.StartsWith("!")).Any(option => !IsApplicableOption(option, Playbook.Options)))) || ((!LiveISO && task.OOBE == OOBESetting.Only && (!ISO || task.ISO != ISOSetting.Only)) || (LiveISO && (task.OOBE == OOBESetting.False || (task.OOBE == null && task.ISO == ISOSetting.True)))) || ((!ISO && task.ISO == ISOSetting.Only && (!LiveISO || task.OOBE != OOBESetting.Only)) || (ISO && task.ISO == ISOSetting.False))) { return null; } var list = new List(); // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop foreach (Tasks.TaskAction taskAction in task.Actions) { var isoCompatibilityError = taskAction.ISO != ISOSetting.False ? taskAction.IsISOCompatible() : null; if (isoCompatibilityError != null) throw new SerializationException(isoCompatibilityError); //if (taskAction.ISO == ISOSetting.Only && taskAction.OOBE == OOBESetting.Only) // throw new SerializationException($"Cannot have both ISO and OOBE set to only on {taskAction.GetType().Name}."); if ((!IsApplicable(upgradingFrom, taskAction.OnUpgrade, taskAction.OnUpgradeVersions, taskAction.PreviousOption ?? taskAction.Option) || !IsApplicableOption(taskAction.Option, options) || !IsApplicableArch(taskAction.Arch, ISO ? isoArch?.ToString() : null)) || (taskAction.Builds != null && ( !taskAction.Builds.Where(build => !build.StartsWith("!")).Any(build => IsApplicableWindowsVersion(build, ISO, isoBuild, isoUpdateBuild)) || taskAction.Builds.Where(build => build.StartsWith("!")).Any(build => !IsApplicableWindowsVersion(build, ISO, isoBuild, isoUpdateBuild)))) || (taskAction.Options != null && ( !taskAction.Options.Where(option => !option.StartsWith("!")).Any(option => IsApplicableOption(option, Playbook.Options)) || taskAction.Options.Where(option => option.StartsWith("!")).Any(option => !IsApplicableOption(option, Playbook.Options)))) || ((!LiveISO && taskAction.OOBE == OOBESetting.Only && (!ISO || taskAction.ISO != ISOSetting.Only)) || (LiveISO && (taskAction.OOBE == OOBESetting.False || (taskAction.OOBE == null && taskAction.ISO == ISOSetting.True)))) || ((!ISO && taskAction.ISO == ISOSetting.Only && (!LiveISO || taskAction.OOBE != OOBESetting.Only)) || (ISO && taskAction.ISO == ISOSetting.False))) { continue; } if (taskAction is Actions.TaskAction taskTaskAction) { if (!File.Exists(Path.Combine(configPath, taskTaskAction.Path))) throw new FileNotFoundException("Could not find YAML file: " + taskTaskAction.Path); try { list.AddRange(ParseActions(configPath, isoBuild, isoUpdateBuild, isoArch, options, taskTaskAction.Path, upgradingFrom) ?? new List()); } catch (Exception e) { if (e is SerializationException exception) returnExceptionMessage += exception.Message + Environment.NewLine + Environment.NewLine; else throw; } } else { list.Add((ITaskAction)taskAction); } } foreach (var childTask in task.Tasks) { if (!File.Exists(Path.Combine(configPath, childTask))) throw new FileNotFoundException("Could not find YAML file: " + childTask); try { list.AddRange(ParseActions(configPath, isoBuild, isoUpdateBuild, isoArch, options, childTask, upgradingFrom) ?? new List()); } catch (Exception e) { if (e is SerializationException exception) returnExceptionMessage += exception.Message + Environment.NewLine + Environment.NewLine; else throw; } } if (!string.IsNullOrEmpty(returnExceptionMessage)) throw new SerializationException(returnExceptionMessage.TrimEnd('\n', '\r')); return list; } catch (YamlException e) { var faultyText = Wrap.ExecuteSafe(() => GetFaultyYamlText(Path.Combine(configPath, file), e), true); if (faultyText.Failed || string.IsNullOrWhiteSpace(faultyText.Value)) { Log.EnqueueExceptionSafe(e); throw new SerializationException(e.Message.TrimEnd('.') + $" in {Path.GetFileName(file)}."); } else { Log.EnqueueExceptionSafe(e, ("YAML", faultyText.Value)); throw new SerializationException(FilterYAMLMessage(e).TrimEnd('.') + $" in {Path.GetFileName(file)}:{Environment.NewLine}{faultyText.Value}"); } } } public static string GetFaultyYamlText(string yamlFilePath, YamlException yamlEx) { using (var reader = new StreamReader(yamlFilePath)) { int currentLine = 0; StringBuilder sb = new StringBuilder(); while (!reader.EndOfStream) { currentLine++; string line = reader.ReadLine(); if (line == null) throw new IndexOutOfRangeException(); var prefix = $"Line {currentLine}: "; if (currentLine == yamlEx.Start.Line) { if (yamlEx.Start.Line == yamlEx.End.Line) { int endIndexInLine = yamlEx.End.Column - Math.Max(0, yamlEx.Start.Column - 1); var text = line.Substring(Math.Max(0, yamlEx.Start.Column - 1), endIndexInLine); if (text.Length <= 1 || string.IsNullOrWhiteSpace(text)) text = line; text = string.Join(Environment.NewLine + prefix.Length, text.SplitByLength(25).Select(x => x.Trim())); sb.Append(prefix + text); break; } else { var text = line.Substring(Math.Max(0, yamlEx.Start.Column - 1)); text = string.Join(Environment.NewLine + prefix.Length, text.SplitByLength(25).Select(x => x.Trim())); sb.Append(prefix + text); } } else if (currentLine > yamlEx.Start.Line && currentLine < yamlEx.End.Line) { var text = string.Join(Environment.NewLine + prefix.Length, line.SplitByLength(25).Select(x => x.Trim())); sb.Append(Environment.NewLine).Append(prefix + text); } else if (currentLine == yamlEx.End.Line) { var text = string.Join(Environment.NewLine + prefix.Length, line.Substring(0, yamlEx.End.Column).SplitByLength(25).Select(x => x.Trim())); sb.Append(Environment.NewLine).Append(prefix + text); break; } } var faultyText = sb.ToString(); return faultyText; } } private static string FilterYAMLMessage(YamlException exception) { int count = 0; int i = 0; for (; i < exception.Message.Length; i++) { if (exception.Message[i] == '(') ++count; else if (exception.Message[i] == ')') --count; if (exception.Message.Length >= i + 1 + 3 && exception.Message.Substring(i + 1, 3) == " - ") { i += 3; continue; } if (count == 0) return exception.Message.Substring(i + 1).Trim().TrimStart(':', ' '); } throw new UnexpectedException(); } public static async Task DoActions(List actions, string logFolder, Action progressReport) { bool errorOccurred = false; foreach (ITaskAction action in actions) { var actionName = action.GetType().ToString().Split('.').Last(); using var writer = new Output.OutputWriter(actionName.Replace("Action", ""), Path.Combine(logFolder, "Output.txt"), Path.Combine(logFolder, "Log.yml")); writer.LogOptions.SourceOverride = actionName; ErrorAction errorAction = ((Tasks.TaskAction)action).ErrorAction ?? action.GetDefaultErrorAction(); var errorString = action.ErrorString(); var retryAllowed = ((Tasks.TaskAction)action).AllowRetries ?? action.GetRetryAllowed(); int i = 0; try { if (!string.IsNullOrWhiteSpace(((Tasks.TaskAction)action).Status) && action.GetType() != typeof(WriteStatusAction)) await new WriteStatusAction() { Status = ((Tasks.TaskAction)action).Status }.RunTask(writer); do { if (i > 0) writer.WriteLineSafe("Warning", "Action detected as unsuccessful. Retrying..."); try { var actionTask = action.RunTask(writer); if (actionTask == null) action.RunTaskOnMainThread(writer); else await actionTask; action.ResetProgress(); } catch (Exception e) { action.ResetProgress(); if (e is ErrorHandlingException errorHandlingException) { if (errorHandlingException.Action == TaskAction.ExitCodeAction.Retry || errorHandlingException.Action == TaskAction.ExitCodeAction.RetryError) { errorString = errorHandlingException.Message; Thread.Sleep(50); i += 2; if (i == 10) { if (errorHandlingException.Action == TaskAction.ExitCodeAction.Retry) { i = 0; break; } if (errorHandlingException.Action == TaskAction.ExitCodeAction.RetryError) { i = 0; throw errorHandlingException; } } continue; } errorAction = errorHandlingException.Action switch { TaskAction.ExitCodeAction.Log => ErrorAction.Log, TaskAction.ExitCodeAction.Error => ErrorAction.Notify, TaskAction.ExitCodeAction.Halt => ErrorAction.Halt, _ => ErrorAction.Log }; errorString = errorHandlingException.Message; ((Tasks.TaskAction)action).IgnoreErrors = false; i = 10; break; } Log.WriteExceptionSafe(e, null, new Log.LogOptions(writer)); List ExceptionBreakList = new List() { "System.ArgumentException", "System.SecurityException", "System.UnauthorizedAccessException", "System.TimeoutException" }; if (ExceptionBreakList.Any(x => x.Equals(e.GetType().ToString())) || !retryAllowed) { i = 10; break; } Thread.Sleep(300); } if (i > 0) Thread.Sleep(50); i++; if (action.GetStatus(writer) == UninstallTaskStatus.Completed) break; } while (i < 10); } catch (Exception e) { if (!((Tasks.TaskAction)action).IgnoreErrors) { if (errorAction == ErrorAction.Log) Log.WriteExceptionSafe(LogType.Info, e, "An ignored error occurred while running an action.", new Log.LogOptions(writer)); if (errorAction == ErrorAction.Notify) { Log.WriteExceptionSafe(e, "An error occurred while running an action.", new Log.LogOptions(writer)); errorOccurred = true; } if (errorAction == ErrorAction.Halt) { Log.WriteExceptionSafe(LogType.Critical, e, "Playbook halted due to a failed critical action.", new Log.LogOptions(writer)); throw e; } } } progressReport(action.GetProgressWeight()); if (i == 10) { if (!((Tasks.TaskAction)action).IgnoreErrors) { if (errorAction == ErrorAction.Log) Log.WriteSafe(LogType.Info, errorString, null, new Log.LogOptions(writer)); if (errorAction == ErrorAction.Notify) { Log.WriteSafe(LogType.Error, errorString, null, new Log.LogOptions(writer)); errorOccurred = true; } if (errorAction == ErrorAction.Halt) { Log.WriteSafe(LogType.Critical, "Playbook halted due to a critical error: " + errorString, null, new Log.LogOptions(writer)); throw new Exception("Critical error: " + errorString); } } } } ProcessPrivilege.ResetTokens(); return errorOccurred; } public static Playbook DeserializePlaybook(string dir) { 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); };*/ try { using (XmlReader reader = XmlReader.Create($"{dir}\\playbook.conf")) { pb = (Playbook)serializer.Deserialize(reader); } } catch (InvalidOperationException e) { if (e.InnerException == null) throw; throw new XmlException(e.Message.TrimEnd('.') + ": " + e.InnerException.Message); } pb.Path = dir; return pb; } [Serializable] public class PlaybookMetadata : Log.ILogMetadata { public PlaybookMetadata(string[] options, string playbookName, string playbookVersion) => (Options, Playbook, Version) = (options, playbookName, playbookVersion); public DateTime CreationTime { get; set; } public string ClientVersion { get; set; } public string WindowsVersion { get; set; } public string SystemLanguage { get; set; } public string UserLanguage { get; set; } public Architecture Architecture { get; set; } public string SystemMemory { get; set; } public int SystemThreads { get; set; } public string FreeSpace { get; set; } // ReSharper disable once MemberHidesStaticFromOuterClass public string Playbook { get; set; } public string Version { get; set; } public string[] Options { get; set; } public virtual void Construct() { ClientVersion = Globals.CurrentVersion; WindowsVersion = $"Windows {Win32.SystemInfoEx.WindowsVersion.MajorVersion} {Win32.SystemInfoEx.WindowsVersion.Edition} {Win32.SystemInfoEx.WindowsVersion.BuildNumber}.{Win32.SystemInfoEx.WindowsVersion.UpdateNumber}"; SystemLanguage = Win32.SystemInfoEx.GetSystemLanguage(); UserLanguage = Win32.SystemInfoEx.GetUserLanguage(); SystemMemory = StringUtils.HumanReadableBytes(Win32.SystemInfoEx.GetSystemMemoryInBytes()); SystemThreads = Environment.ProcessorCount; FreeSpace = StringUtils.HumanReadableBytes(Win32.SystemInfoEx.GetFreeDiskSpaceInBytes()); Architecture = Win32.SystemInfoEx.SystemArchitecture; CreationTime = DateTime.UtcNow; } public string Serialize(ISerializer serializer) => serializer.Serialize(this); } private static void CreateWindowsSkeleton(string folder) { foreach (var directory in new [] { @"Windows\System32\OOBE", @"Windows\TEMP", @"ProgramData\Microsoft\Windows", @"Program Files", @"Program Files (x86)", @"Users\Public\Documents", @"Users\Public\Downloads", @"Users\Public\Desktop", @"Users\Public\Libraries", @"Users\Public\Music", @"Users\Public\Videos", @"Users\Public\Pictures", @"Users\Default\Documents", @"Users\Default\Downloads", @"Users\Default\Desktop", @"Users\Default\Libraries", @"Users\Default\Music", @"Users\Default\Videos", @"Users\Default\Pictures", @"Users\Default\AppData\Roaming\Microsoft\Windows\Start Menu\Programs", @"Users\Default\AppData\Local\Microsoft", @"Users\Default\Desktop", @"Users\Default\Libraries", @"Users\Default\Music", @"Users\Default\Videos", @"Users\Default\Pictures" }) { Directory.CreateDirectory(Path.Combine(folder, directory)); var realFolder = Path.Combine(Environment.GetEnvironmentVariable("SYSTEMDRIVE")!, folder); if (!Directory.Exists(realFolder)) continue; Wrap.ExecuteSafe(() => { DirectoryInfo aclFolder = new DirectoryInfo(realFolder); do { var target = new DirectoryInfo(aclFolder.FullName.Replace(Environment.GetEnvironmentVariable("SYSTEMDRIVE")!, folder)); target.SetAccessControl(aclFolder.GetAccessControl()); } while ((aclFolder = aclFolder.Parent) != null); }, true); } } [InterprocessMethod(Level.TrustedInstaller)] public static async Task RunPlaybook(string playbookPath, bool verified, bool autoLogon, [CanBeNull] string username, [CanBeNull] string password, [CanBeNull] string adminPassword, string playbookName, string playbookVersion, string[] options, string[] allOptions, string logFolder, InterLink.InterProgress progress, [CanBeNull] InterLink.InterMessageReporter statusReporter, bool useKernelDriver) => await RunPlaybook(playbookPath, false, false, false, verified, autoLogon, username, password, adminPassword, false, null, null, null, null, null, playbookName, playbookVersion, options, allOptions, logFolder, progress, statusReporter, useKernelDriver); [InterprocessMethod(Level.TrustedInstaller)] public static async Task RunPlaybook(string playbookPath, bool networkDrivers, bool graphicsDrivers, bool systemDrivers, bool verified, bool autoLogon, [CanBeNull] string username, [CanBeNull] string password, [CanBeNull] string adminPassword, bool esd, string isoDest, [CanBeNull] string isoPath, [CanBeNull] string isoBuild, [CanBeNull] string isoUpdateBuild, Architecture? isoArch, string playbookName, string playbookVersion, string[] options, string[] allOptions, string logFolder, InterLink.InterProgress progress, [CanBeNull] InterLink.InterMessageReporter statusReporter, bool useKernelDriver) { Log.LogFileOverride = Path.Combine(logFolder, "Log.yml"); Log.MetadataSource = new PlaybookMetadata(options, playbookName, playbookVersion); ISO = isoPath != null; WriteStatusAction.StatusReporter = statusReporter; if (ISO) ThrowIfNotEnoughFreeSpace(isoPath, Path.GetTempPath()); AmeliorationUtil.UseKernelDriver = useKernelDriver; AmeliorationUtil.Playbook = AmeliorationUtil.DeserializePlaybook(playbookPath); AmeliorationUtil.Playbook.Options = options?.ToList(); var extractedIso = Path.Combine(Path.GetTempPath(), "AME-ISO-" + Guid.NewGuid()); var mountGuidString = Guid.NewGuid().ToString().Replace("-", "").Replace("{", "").Replace("}", ""); var winMount = Path.Combine(Path.GetPathRoot(Path.GetTempPath()), "ISO-" + mountGuidString); var wimMount = Path.Combine(Path.GetPathRoot(Path.GetTempPath()), "WIM-" + mountGuidString); var wimStaging = Path.Combine(Path.GetPathRoot(Path.GetTempPath()), "TMP-" + mountGuidString); if (ISO) { await new WriteStatusAction() { Status = "Extracting Image" }.RunTask(Output.OutputWriter.Null); var extractProgress = new Progress(value => { if (value <= 100) progress.Report(value / 20); }); if (isoPath != null) await iso_mode.ISO.WriteISO(isoPath, extractedIso, extractProgress); } bool unhooked = false; try { if (ISO) { await new WriteStatusAction() { Status = "Extracting WIM" }.RunTask(Output.OutputWriter.Null); try { Wim.GlobalInit("libwim-15.dll", InitFlags.None); } catch (InvalidOperationException e) { // Already initialized } if (Playbook.ISO?.DisableHardwareRequirements == true && File.Exists(Path.Combine(extractedIso, @"sources\boot.wim"))) { using var bootWim = Wim.OpenWim(Path.Combine(extractedIso, @"sources\boot.wim"), OpenFlags.None); int image = bootWim.GetWimInfo().BootIndex == 0 ? 1 : (int)bootWim.GetWimInfo().BootIndex; var systemHivePath = Path.Combine(Path.GetTempPath(), "AME-BOOTWIM-" + ISOGuid, "SYSTEM"); bootWim.ExtractPath(image, Path.GetDirectoryName(systemHivePath), @"Windows\System32\config\SYSTEM", ExtractFlags.NoPreserveDirStructure); if (Wrap.ExecuteSafe(() => WinUtil.RegistryManager.HookHive("BOOT-" + ISOGuid, systemHivePath), true) == null) { using (var labKey = Registry.Users.CreateSubKey("BOOT-" + ISOGuid + @"\Setup\LabConfig")) { labKey.SetValue("BypassRAMCheck", 1, RegistryValueKind.DWord); labKey.SetValue("BypassSecureBootCheck", 1, RegistryValueKind.DWord); labKey.SetValue("BypassCPUCheck", 1, RegistryValueKind.DWord); labKey.SetValue("BypassTPMCheck", 1, RegistryValueKind.DWord); } if (Wrap.ExecuteSafe(() => WinUtil.RegistryManager.UnhookHive("BOOT-" + ISOGuid), true) == null) { bootWim.UpdateImage( image, UpdateCommand.SetAdd(systemHivePath, @"Windows\System32\config\SYSTEM", null, AddFlags.None), UpdateFlags.None); bootWim.Overwrite(WriteFlags.None, Wim.DefaultThreads); } } } if (Playbook.ISO?.DisableBitLocker == true) { Directory.CreateDirectory(Path.Combine(extractedIso, @"sources\$OEM$\$$\Panther")); File.WriteAllText(Path.Combine(extractedIso, @"sources\$OEM$\$$\Panther\unattend.xml"), ISOWIM.GenerateUnattendXml(isoArch switch { Architecture.X86 => "x86", Architecture.X64 => "amd64", Architecture.Arm => "arm", Architecture.Arm64 => "arm64", }, true, true)); } var wimPath = Path.Combine(extractedIso, @"sources\install.wim"); bool wimExists = File.Exists(wimPath); if (!wimExists) { using (var esdWim = WimWrapper.OpenWim(Path.Combine(extractedIso, @"sources\install.esd"))) { esdWim.WriteToWIM(Path.Combine(extractedIso, @"sources\install.wim"), wimStaging); } File.Delete(Path.Combine(extractedIso, @"sources\install.esd")); } WimInstance = WimWrapper.OpenWim(Path.Combine(extractedIso, @"sources\install.wim")); WimInstance.RemoveSuperfluousImages(); WimInstance.WriteChanges(); Directory.CreateDirectory(winMount); CreateWindowsSkeleton(winMount); //wim.ExtractImage(wim.GetWimInfo().BootIndex == 0 ? 1 : (int)wim.GetWimInfo().BootIndex, mountedWim, ExtractFlags.None); progress.Report(8); WimPath = winMount; ISOGuid = mountGuidString; if (Playbook.Requirements.Contains(Requirements.Requirement.DefenderDisabled)) { var nameList = new List(); for (int i = 1; i <= WimInstance.ImageCount; i++) { nameList.Add(WimInstance.GetImageName(i)); } for (int i = 1; i <= WimInstance.ImageCount; i++) { await new WriteStatusAction() { Status = "Mounting " + nameList[i - 1] }.RunTask(Output.OutputWriter.Null); Directory.CreateDirectory(wimMount); WimInstance.Mount(i, wimMount, wimStaging); try { await new WriteStatusAction() { Status = "Applying Defender Package" }.RunTask(Output.OutputWriter.Null); var cabPath = ExtractCab(isoArch ?? Architecture.X64); using var writer = new Output.OutputWriter("Run", Path.Combine(logFolder, "Output.txt"), Path.Combine(logFolder, "Log.yml")); writer.LogOptions.SourceOverride = "Run"; var action = new RunAction() { Exe = "DISM", Arguments = $"/Image:\"{wimMount}\" /Add-Package /PackagePath:\"{cabPath}\" /NoRestart /IgnoreCheck", }; action.RunTaskOnMainThread(writer); Wrap.ExecuteSafe(() => File.Delete(cabPath), true); if (action.ExitCode != 0 && action.ExitCode != 3010) throw new Exception("Failed to apply Defender package."); } finally { WimInstance.Unmount(); } } } //await new WriteStatusAction() { Status = "Mounting Amogus" }.RunTask(Output.OutputWriter.Null); //Thread.Sleep(TimeSpan.FromSeconds(100)); WimInstance.MountHives(ISOGuid); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection", Value = "DisableRealtimeMonitoring", Data = 1, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\Windows Defender\Real-Time Protection", Value = "DisableRealtimeMonitoring", Data = 1, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); if (Playbook.Requirements.Contains(Requirements.Requirement.DefenderToggled)) { await new Actions.RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\Windows Defender\Real-Time Protection", Value = "DisableAsyncScanOnOpen", Data = 1, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\Windows Defender\SpyNet", Value = "SpyNetReporting", Data = 0, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\Windows Defender\SpyNet", Value = "SubmitSamplesConsent", Data = 0, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\Windows Defender\Features", Value = "TamperProtection", Data = 4, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); } /* using (var wimDumpKey = Registry.Users.CreateSubKey("HKLM-SOFTWARE-" + ISOGuid + @"\Microsoft\Windows\Windows Error Reporting\LocalDumps")) { wimDumpKey.SetValue("DumpType", 2, RegistryValueKind.DWord); wimDumpKey.SetValue("DumpFolder", @"C:\CrashDumps", RegistryValueKind.ExpandString); } */ progress.Report(systemDrivers || graphicsDrivers || networkDrivers ? 10 : 20); CopyDirectory(Path.Combine(playbookPath), Path.Combine(WimPath, @"ProgramData\AME\OOBE\Playbook")); if (File.Exists(Path.Combine(WimPath, @"ProgramData\AME\OOBE\playbook.apbx"))) File.Delete(Path.Combine(WimPath, @"ProgramData\AME\OOBE\playbook.apbx")); File.Move(Path.Combine(Directory.GetCurrentDirectory(), "oobe_playbook.apbx"), Path.Combine(WimPath, @"ProgramData\AME\OOBE\playbook.apbx")); XmlSerializer serializerIso = new XmlSerializer(typeof(ISO)); using (XmlWriter writer = XmlWriter.Create(Path.Combine(extractedIso, @"iso.conf"), new XmlWriterSettings() {Indent = true})) { serializerIso.Serialize(writer, new ISO() { Name = Playbook.Name, Creator = Playbook.Username, UniqueId = Playbook.UniqueId, Version = Playbook.Version, WindowsVersion = isoBuild, WindowsUpdateVersion = isoUpdateBuild, Options = options ?? Array.Empty(), BitLockerDisabled = Playbook.ISO?.DisableBitLocker ?? false, HardwareRequirementsDisabled = Playbook.ISO?.DisableHardwareRequirements ?? false, InternetRequired = Playbook.OOBE?.Internet == OOBE.InternetRequirementLevel.Force, }); } } Playbook upgradingFrom = null; if (!ISO) { Playbook[] appliedPlaybooks = Playbook.GetAppliedPlaybooks(); upgradingFrom = Playbook.LastAppliedMatch(appliedPlaybooks); if (upgradingFrom != null && (!Playbook.IsUpgradeApplicable(upgradingFrom.Version) && !(upgradingFrom.GetVersionNumber() <= Playbook.GetVersionNumber()))) upgradingFrom = null; } List actions = ParseActions($"{Playbook.Path}\\Configuration", isoBuild, isoUpdateBuild, isoArch, Playbook.Options, File.Exists($"{Playbook.Path}\\Configuration\\main.yml") ? "main.yml" : "custom.yml", upgradingFrom); if (actions == null) throw new SerializationException("No applicable tasks were found in the Playbook."); bool errorOccurred = false; if (ISO) { if (Playbook.Software == null) Playbook.Software = Array.Empty(); Directory.CreateDirectory(Path.Combine(WimPath, @"ProgramData\AME\OOBE")); XmlSerializer serializerOobe = new XmlSerializer(typeof(OOBE)); using (XmlWriter writer = XmlWriter.Create(Path.Combine(WimPath, @"ProgramData\AME\OOBE\oobe.conf"), new XmlWriterSettings() {Indent = true})) { serializerOobe.Serialize(writer, new OOBE() { Username = username, Password = password, AdminPassword = adminPassword, AdminUserEnabled = options.Contains("security-enhanced"), BulletPoints = Playbook.OOBE.BulletPoints.Select(x => new BulletPoint() {Icon = x.Icon, Title = x.Title, Description = x.Description}).ToList(), AutoLogon = autoLogon, Verified = verified, Options = options ?? Array.Empty(), InternetRequirement = Playbook.OOBE.Internet, Software = Playbook.Software.Where(x => string.IsNullOrEmpty(x.Option) || IsApplicableOption(x.Option, Playbook.Options)).Select(x => new OOBESoftware() { Name = x.Name, Title = x.Title, Description = x.Description, IconPath = File.Exists(Path.Combine(Playbook.Path, "Images", x.Icon)) ? x.Icon : Directory.GetFiles(Path.Combine(Playbook.Path, "Images"), x.Icon).FirstOrDefault(file => file.StartsWith(x.Icon, StringComparison.OrdinalIgnoreCase)), IsDefaultWebBrowser = x.DefaultWebBrowser, Local = x.Local, }).ToList() }); } try { if (systemDrivers || networkDrivers || graphicsDrivers) { await DriverManager.HandleDrivers(driversProgress => progress.Report((decimal)(driversProgress / 10) + 10), statusReporter, graphicsDrivers, networkDrivers, systemDrivers, "https://download.ameliorated.io/drivers.json", Environment.ExpandEnvironmentVariables(@"%PROGRAMDATA%\AME\DriverCache"), Path.Combine(WimPath, @"ProgramData\AME\OOBE\Drivers")); } } catch (Exception e) { Log.EnqueueExceptionSafe(e); errorOccurred = true; } } if (UseKernelDriver) { //Check if KPH is installed. ServiceController service = ServiceController.GetDevices() .FirstOrDefault(s => s.DisplayName == "KProcessHacker2"); if (service == null) { //Installs KPH await WinUtil.RemoveProtectionAsync(); } } var totalProgress = Math.Max(GetProgressMaximum(actions), 1); var progressLeft = totalProgress; Action progressReport = addition => { progressLeft -= addition; var progressValue = 1 - ((decimal)progressLeft / totalProgress); if (isoPath != null) progressValue = progressValue * 0.75M; progress.Report(isoPath == null ? progressValue * 100 : 10 + (progressValue * 100)); }; WriteStatusAction.StatusReporter = statusReporter; try { Environment.SetEnvironmentVariable("OOBE", LiveISO ? "true" : "false", EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("ISO", ISO ? "true" : "false", EnvironmentVariableTarget.Process); try { errorOccurred = await DoActions(actions, logFolder, progressReport); } finally { WinUtil.RegistryManager.UnhookUserHives(); } //Check if the kernel driver is installed. //service = ServiceController.GetDevices() //.FirstOrDefault(s => s.DisplayName == "KProcessHacker2"); if (UseKernelDriver) { //Remove Process Hacker's kernel driver. await WinUtil.UninstallDriver(); CoreActions.SafeRun(new Core.Actions.RegistryKeyAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2", }); } if (ISO) { progress.Report(85); await new WriteStatusAction() { Status = "Saving Image" }.RunTask(Output.OutputWriter.Null); await InjectOOBE(); WimInstance.UnmountHives(ISOGuid, true); //await new WriteStatusAction() { Status = "Mounting AmogusXXX" }.RunTask(Output.OutputWriter.Null); //Thread.Sleep(TimeSpan.FromSeconds(60)); unhooked = true; foreach (string directory in Directory.GetDirectories(winMount)) { // We can't use AddTree on root directory because it messes up wim image name and other properties :( WimInstance.AddTree(directory, @"\" + Path.GetFileName(directory)); } if (!esd) WimInstance.WriteToWIM(Path.Combine(extractedIso, @"sources\install.wim"), wimStaging); else { WimInstance.WriteToESD(Path.Combine(extractedIso, @"sources\install.esd")); WimInstance.Dispose(); File.Delete(Path.Combine(extractedIso, @"sources\install.wim")); } progress.Report(96); await new WriteStatusAction() { Status = "Cleaning WIM" }.RunTask(Output.OutputWriter.Null); Wrap.ExecuteSafe(() => Directory.Delete(WimPath, true), true); progress.Report(97); await new WriteStatusAction() { Status = "Creating ISO" }.RunTask(Output.OutputWriter.Null); var runAction = new RunAction() { HandleExitCodes = new Dictionary() {{"!0", TaskAction.ExitCodeAction.Error}}, BaseDir = true, Exe = "mkisofs.exe", Arguments = $@"-iso-level 4 -l -R -UDF -D -volid ""ISO"" -b boot/etfsboot.com -no-emul-boot -boot-load-size 8 -hide boot.catalog -eltorito-alt-boot -eltorito-platform efi -no-emul-boot -b efi/microsoft/boot/efisys.bin -o ""{isoDest}"" ""{extractedIso}""" }; using var writer = new Output.OutputWriter("ISO Creator", Path.Combine(logFolder, "Output.txt"), Path.Combine(logFolder, "Log.yml")); writer.LogOptions.SourceOverride = "ISO Creator"; runAction.RunTaskOnMainThread(writer); progress.Report(98); await new WriteStatusAction() { Status = "Cleaning Up" }.RunTask(Output.OutputWriter.Null); } return errorOccurred; } catch (Exception e) { if (!ISO) { Wrap.ExecuteSafe(() => { WriteAppliedPlaybook(playbookPath, ISO ? Registry.Users.OpenSubKey("HKLM-" + mountGuidString) : null, ISO ? WimPath : null, Playbook.UniqueId, Playbook.Name, Playbook.Username, Playbook.Overhaul, Playbook.Version, options ?? Array.Empty(), allOptions, true, true, verified); }, true); } throw; } } finally { if (ISO) { if (!unhooked) Wrap.ExecuteSafe(() => WimInstance.UnmountHives(ISOGuid), true); WimInstance?.Dispose(); if (Directory.Exists(WimPath)) { Wrap.ExecuteSafe(() => Directory.Delete(WimPath)); Wrap.ExecuteSafe(() => { DirectoryInfo dir = new DirectoryInfo(WimPath); foreach (FileInfo file in dir.GetFiles("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => file.Attributes = FileAttributes.Normal); foreach (DirectoryInfo subDir in dir.GetDirectories("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => subDir.Attributes = FileAttributes.Normal); Wrap.ExecuteSafe(() => dir.Attributes = FileAttributes.Normal); }); Wrap.ExecuteSafe(() => Directory.Delete(WimPath, true)); } if (Directory.Exists(wimMount)) { Wrap.ExecuteSafe(() => Directory.Delete(wimMount)); Wrap.ExecuteSafe(() => { DirectoryInfo dir = new DirectoryInfo(wimMount); foreach (FileInfo file in dir.GetFiles("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => file.Attributes = FileAttributes.Normal); foreach (DirectoryInfo subDir in dir.GetDirectories("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => subDir.Attributes = FileAttributes.Normal); Wrap.ExecuteSafe(() => dir.Attributes = FileAttributes.Normal); }); Wrap.ExecuteSafe(() => Directory.Delete(wimMount, true)); } if (Directory.Exists(wimStaging)) { Wrap.ExecuteSafe(() => Directory.Delete(wimStaging)); Wrap.ExecuteSafe(() => { DirectoryInfo dir = new DirectoryInfo(wimStaging); foreach (FileInfo file in dir.GetFiles("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => file.Attributes = FileAttributes.Normal); foreach (DirectoryInfo subDir in dir.GetDirectories("*", SearchOption.AllDirectories)) Wrap.ExecuteSafe(() => subDir.Attributes = FileAttributes.Normal); Wrap.ExecuteSafe(() => dir.Attributes = FileAttributes.Normal); }); Wrap.ExecuteSafe(() => Directory.Delete(wimStaging, true)); } Wrap.ExecuteSafe(() => Directory.Delete(extractedIso, true), true); } } } private static async Task InjectOOBE() { string destination = Path.Combine(WimPath, @"ProgramData\AME\OOBE\OOBE.exe"); Assembly assembly = Assembly.GetExecutingAssembly(); using (UnmanagedMemoryStream stream = (UnmanagedMemoryStream)assembly!.GetManifestResourceStream($"TrustedUninstaller.Shared.Properties.AME.Client.exe")) { byte[] buffer = new byte[stream!.Length]; stream.Read(buffer, 0, buffer.Length); File.WriteAllBytes(destination, buffer); } destination = Path.Combine(WimPath, @"Windows\system32\OOBE\msoobe.exe"); WimInstance.MoveFileOrFolder(@"Windows\system32\OOBE\msoobe.exe", @"Windows\system32\OOBE\msoobeext.exe"); using (UnmanagedMemoryStream stream = (UnmanagedMemoryStream)assembly!.GetManifestResourceStream($"TrustedUninstaller.Shared.Properties.oobe_shim.exe")) { byte[] buffer = new byte[stream!.Length]; stream.Read(buffer, 0, buffer.Length); File.WriteAllBytes(destination, buffer); } await new Actions.RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\ameoobe", Value = "DisplayName", Data = "AME OOBE", Type = RegistryValueType.REG_SZ }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\ameoobe", Value = "ObjectName", Data = "LocalSystem", Type = RegistryValueType.REG_SZ }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\ameoobe", Value = "ImagePath", Data = "\"%ProgramData%\\AME\\OOBE\\OOBE.exe\" --service", Type = RegistryValueType.REG_EXPAND_SZ }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\ameoobe", Value = "ErrorControl", Data = 0, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\ameoobe", Value = "Start", Data = 2, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); await new Actions.RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\ameoobe", Value = "Type", Data = 16, Type = RegistryValueType.REG_DWORD }.RunTask(Output.OutputWriter.Null); } private static string ExtractCab(Architecture arch) { var cabArch = arch == Architecture.Arm || arch == Architecture.Arm64 ? "arm64" : "amd64"; var fileDir = Environment.ExpandEnvironmentVariables("%ProgramData%\\AME"); if (!Directory.Exists(fileDir)) Directory.CreateDirectory(fileDir); var destination = Path.Combine(fileDir, $"Z-AME-NoDefender-Package31bf3856ad364e35{cabArch}1.0.0.0.cab"); if (File.Exists(destination)) { return destination; } Assembly assembly = Assembly.GetExecutingAssembly(); using (UnmanagedMemoryStream stream = (UnmanagedMemoryStream)assembly!.GetManifestResourceStream($"TrustedUninstaller.Shared.Properties.Z-AME-NoDefender-Package31bf3856ad364e35{cabArch}1.0.0.0.cab")) { byte[] buffer = new byte[stream!.Length]; stream.Read(buffer, 0, buffer.Length); File.WriteAllBytes(destination, buffer); } return destination; } private static int RunPSCommand(string command, [CanBeNull] DataReceivedEventHandler outputHandler, [CanBeNull] DataReceivedEventHandler errorHandler) => RunCommand("powershell.exe", $"-NoP -C \"{command}\"", outputHandler, errorHandler); private static int RunCommand(string exe, string arguments, [CanBeNull] DataReceivedEventHandler outputHandler, [CanBeNull] DataReceivedEventHandler errorHandler) { var process = new Process { StartInfo = new ProcessStartInfo() { FileName = exe, Arguments = arguments, CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = outputHandler != null, RedirectStandardError = errorHandler != null } }; if (outputHandler != null) process.OutputDataReceived += outputHandler; if (errorHandler != null) process.ErrorDataReceived += errorHandler; process.Start(); if (outputHandler != null) process.BeginOutputReadLine(); if (errorHandler != null) process.BeginErrorReadLine(); process.WaitForExit(); return process.ExitCode; } static void ThrowIfNotEnoughFreeSpace(string isoPath, string destination) { if (!File.Exists(isoPath)) throw new FileNotFoundException($"The file '{isoPath}' does not exist."); long fileSize = new FileInfo(isoPath).Length * 4; DriveInfo tempDrive = new DriveInfo(Path.GetPathRoot(destination)); long freeSpace = tempDrive.AvailableFreeSpace; if (freeSpace < fileSize) throw new IOException($"Not enough free space on the drive. Required: {fileSize} bytes, Available: {freeSpace} bytes."); } private static void DeleteAppliedPlaybook(string folderName) { var appliedDir = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AME\AppliedPlaybooks"); if (Directory.Exists(Path.Combine(appliedDir, folderName))) Directory.Delete(Path.Combine(appliedDir, folderName), true); } private static void WriteAppliedPlaybook(string playbookPath, [CanBeNull] RegistryKey rootKey, [CanBeNull] string ameRoot, Guid? uniqueId, string name, string username, bool overhaul, string version, [NotNull] string[] selectedOptions, [NotNull] string[] allOptions, bool hadErrors, bool fatalError, bool isVerified) { try { if (uniqueId != null) { using var key = (rootKey ?? Registry.LocalMachine).CreateSubKey(@$"SOFTWARE\AME\Playbooks\Applied\{{{uniqueId.Value.ToString().ToUpper()}}}", true); key.SetValue("Name", name, RegistryValueKind.String); key.SetValue("Username", username, RegistryValueKind.String); key.SetValue("Overhaul", overhaul ? 1 : 0, RegistryValueKind.DWord); key.SetValue("Version", version, RegistryValueKind.String); key.SetValue("ErrorLevel", hadErrors ? fatalError ? 2 : 1 : 0, RegistryValueKind.DWord); key.SetValue("AvailableOptions", allOptions, RegistryValueKind.MultiString); key.SetValue("SelectedOptions", selectedOptions, RegistryValueKind.MultiString); key.SetValue("AppliedTimeUTC", DateTime.UtcNow.ToBinary(), RegistryValueKind.QWord); if (File.Exists(Path.Combine(playbookPath, "playbook.png"))) key.SetValue("Image", File.ReadAllBytes(Path.Combine(playbookPath, "playbook.png")), RegistryValueKind.Binary); else if (File.Exists(Path.Combine(playbookPath, "Images\\playbook.png"))) key.SetValue("Image", File.ReadAllBytes(Path.Combine(playbookPath, "Images\\playbook.png")), RegistryValueKind.Binary); } else { var parent = Directory.CreateDirectory(ameRoot != null ? Path.Combine(ameRoot, "AppliedPlaybooks") : Environment.ExpandEnvironmentVariables(@"%ProgramData%\AME\AppliedPlaybooks")); var indexes = parent.GetDirectories().Where(v => int.TryParse(v.Name, out _)).Select(v => int.Parse(v.Name)).ToList(); var currentIndex = indexes.Count > 0 ? indexes.Max() : 0; if (currentIndex >= 10) Wrap.ExecuteSafe(() => parent.GetDirectories().First().Delete(true), true); var target = parent.CreateSubdirectory((currentIndex + 1).ToString()); if (File.Exists(Path.Combine(playbookPath, "playbook.png"))) File.Copy(Path.Combine(playbookPath, "playbook.png"), Path.Combine(target.FullName, "playbook.png")); else if (File.Exists(Path.Combine(playbookPath, "Images\\playbook.png"))) File.Copy(Path.Combine(playbookPath, "Images\\playbook.png"), Path.Combine(target.FullName, "playbook.png")); File.Copy(Path.Combine(playbookPath, "playbook.conf"), Path.Combine(target.FullName, "playbook.conf")); if (hadErrors) File.Create(Path.Combine(target.FullName, "errors.txt")).Close(); if (isVerified) File.Create(Path.Combine(target.FullName, "verified.txt")).Close(); } } catch (Exception e) { Log.EnqueueExceptionSafe(LogType.Warning, e); } } static void CopyDirectory(string sourceDir, string destDir) { if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir); foreach (string file in Directory.GetFiles(sourceDir)) { string destFile = Path.Combine(destDir, Path.GetFileName(file)); File.Copy(file, destFile, true); } foreach (string subDir in Directory.GetDirectories(sourceDir)) { string newDestSubDir = Path.Combine(destDir, Path.GetFileName(subDir)); CopyDirectory(subDir, newDestSubDir); } } public static async Task DownloadLanguagesAsync(IEnumerable langsSelected) { foreach (var lang in langsSelected) { var lowerLang = lang.ToLower(); var arch = RuntimeInformation.OSArchitecture; var winVersion = Environment.OSVersion.Version.Build; var convertedArch = ""; switch (arch) { case Architecture.X64: convertedArch = "amd64"; break; case Architecture.Arm64: convertedArch = "arm64"; break; case Architecture.X86: convertedArch = "x86"; break; } var uuidOfWindowsVersion = ""; var uuidResponse = await Client.GetAsync( $"https://api.uupdump.net/listid.php?search={winVersion}%20{convertedArch}&sortByDate=1"); switch (uuidResponse.StatusCode) { //200 Status code case HttpStatusCode.OK: { var result = uuidResponse.Content.ReadAsStringAsync().Result; //Gets the UUID of the first build object in the response, we take the first since it's the newest. uuidOfWindowsVersion = (string)(JToken.Parse(result)["response"]?["builds"]?.Children().First() .Children().First().Last()); break; } //400 Status code case HttpStatusCode.BadRequest: { var result = uuidResponse.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Bad request.\r\nError:{data["response"]["error"]}"); break; } //429 Status code case (HttpStatusCode)429: { var result = uuidResponse.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Too many requests, try again later.\r\nError:{data["response"]["error"]}"); break; } //500 Status code case HttpStatusCode.InternalServerError: { var result = uuidResponse.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Internal Server Error.\r\nError:{data["response"]["error"]}"); break; } default: throw new ArgumentOutOfRangeException(); } var responseString = await Client.GetAsync( $"https://api.uupdump.net/get.php?id={uuidOfWindowsVersion}&lang={lowerLang}"); switch (responseString.StatusCode) { //200 Status code case HttpStatusCode.OK: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); //Add different urls to different packages to a list var urls = new Dictionary { { "basic", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-basic-{lowerLang}-package-{convertedArch}.cab"] [ "url"] }, { "hw", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-handwriting-{lowerLang}-package-{convertedArch}.cab"] [ "url"] }, { "ocr", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-ocr-{lowerLang}-package-{convertedArch}.cab"][ "url"] }, { "speech", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-speech-{lowerLang}-package-{convertedArch}.cab"] [ "url"] }, { "tts", (string) data["response"]["files"][ $"microsoft-windows-languagefeatures-texttospeech-{lowerLang}-package-{convertedArch}.cab"] [ "url"] } }; var amePath = Path.Combine(Path.GetTempPath(), "AME\\"); //Create the directory if it doesn't exist. var file = new FileInfo(amePath); file.Directory?.Create(); //Does nothing if the directory already exists //Final result being "temp\AME\Languages\file.cab" var downloadPath = Path.Combine(amePath, "Languages\\"); file = new FileInfo(downloadPath); file.Directory?.Create(); using (var webClient = new WebClient()) { Console.WriteLine($"Downloading {lowerLang}.cab file, please wait.."); foreach (var url in urls) { //Check if the file exists, if it does exist, skip it. if (File.Exists(Path.Combine(downloadPath, $"{url.Key}_{lowerLang}.cab"))) { Console.WriteLine($"{url.Key}_{lowerLang} already exists, skipping."); continue; } //Output file format: featureName_languageCode.cab: speech_de-de.cab webClient.DownloadFile(url.Value, $@"{downloadPath}\{url.Key}_{lowerLang}.cab"); } } break; } //400 Status code case HttpStatusCode.BadRequest: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Bad request.\r\nError:{data["response"]["error"]}"); break; } //429 Status code case (HttpStatusCode)429: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Too many requests, try again later.\r\nError:{data["response"]["error"]}"); break; } //500 Status code case HttpStatusCode.InternalServerError: { var result = responseString.Content.ReadAsStringAsync().Result; dynamic data = JObject.Parse(result); Console.WriteLine($"Internal Server Error.\r\nError:{data["response"]["error"]}"); break; } default: throw new ArgumentOutOfRangeException(); } } } private static bool IsApplicableUpgrade(string oldVersion, string allowedVersion) { var oldVersionNumber = VersionNumber.GetVersionNumber(oldVersion); var version = allowedVersion; bool negative = false; if (version.StartsWith("!")) { version = version.TrimStart('!'); negative = true; } bool result = false; if (version.StartsWith(">=")) { var parsed = VersionNumber.GetVersionNumber(version.Substring(2)); if (oldVersionNumber >= parsed) result = true; } else if (version.StartsWith("<=")) { var parsed = VersionNumber.GetVersionNumber(version.Substring(2)); if (oldVersionNumber <= parsed) result = true; } else if (version.StartsWith(">")) { var parsed = VersionNumber.GetVersionNumber(version.Substring(1)); if (oldVersionNumber > parsed) result = true; } else if (version.StartsWith("<")) { var parsed = VersionNumber.GetVersionNumber(version.Substring(1)); if (oldVersionNumber < parsed) result = true; } else { var parsed = VersionNumber.GetVersionNumber(version); if (oldVersionNumber == parsed) result = true; } return negative ? !result : result; } public static bool IsApplicableWindowsVersion(string version, bool ISO, [CanBeNull] string targetISOVersion = null, [CanBeNull] string targetISOUpdateVersion = null) { bool negative = false; if (version.StartsWith("!")) { version = version.TrimStart('!'); negative = true; } bool result = false; bool compareUpdateBuild = version.Contains("."); if ((ISO && targetISOUpdateVersion == null) && compareUpdateBuild) version = version.Split('.').First(); decimal currentBuild; if (ISO) { if (targetISOVersion != null) currentBuild = decimal.Parse(targetISOUpdateVersion == null ? targetISOVersion : targetISOVersion + "." + targetISOUpdateVersion, CultureInfo.InvariantCulture); else return false; } else currentBuild = decimal.Parse(compareUpdateBuild ? Win32.SystemInfoEx.WindowsVersion.BuildNumber + "." + Win32.SystemInfoEx.WindowsVersion.UpdateNumber : Win32.SystemInfoEx.WindowsVersion.BuildNumber.ToString(), CultureInfo.InvariantCulture); if (version.StartsWith(">=")) { var parsed = decimal.Parse(version.Substring(2), CultureInfo.InvariantCulture); if (currentBuild >= parsed) result = true; } else if (version.StartsWith("<=")) { var parsed = decimal.Parse(version.Substring(2), CultureInfo.InvariantCulture); if (currentBuild <= parsed) result = true; } else if (version.StartsWith(">")) { var parsed = decimal.Parse(version.Substring(1), CultureInfo.InvariantCulture); if (currentBuild > parsed) result = true; } else if (version.StartsWith("<")) { var parsed = decimal.Parse(version.Substring(1), CultureInfo.InvariantCulture); if (currentBuild < parsed) result = true; } else { var parsed = decimal.Parse(version, CultureInfo.InvariantCulture); if (currentBuild == parsed) result = true; } return negative ? !result : result; } private static bool IsApplicableOption(string option, List options) { if (String.IsNullOrEmpty(option)) return true; if (option.Contains("&")) { if (option.Contains("!")) throw new ArgumentException("YAML options item must not contain both & and !", "options"); return option.Split('&').All(splitOption => IsApplicableOption(splitOption, options)); } bool negative = false; if (option.StartsWith("!")) { option = option.TrimStart('!'); negative = true; } if (options == null) return negative ? true : false; var result = options.Contains(option, StringComparer.OrdinalIgnoreCase); return negative ? !result : result; } private static bool IsApplicableArch(string arch, [CanBeNull] string isoArch) { if (String.IsNullOrEmpty(arch)) return true; bool negative = false; if (arch.StartsWith("!")) { arch = arch.TrimStart('!'); negative = true; } var result = String.Equals(arch, isoArch ?? Win32.SystemInfoEx.SystemArchitecture.ToString(), StringComparison.OrdinalIgnoreCase); return negative ? !result : result; } } } ================================================ FILE: TrustedUninstaller.Shared/AugmentedProcess.cs ================================================ using System.Text; using System.Threading; using System.Runtime.InteropServices; using System.ComponentModel; using System.ComponentModel.Design; using System.Runtime.CompilerServices; using System.Diagnostics; using System; using System.Collections; using System.Collections.Generic; using System.IO; using Microsoft.Win32.SafeHandles; using System.Collections.Specialized; using System.Globalization; using System.Security; using System.Security.Permissions; using System.Runtime.Versioning; using System.Runtime.ConstrainedExecution; using Microsoft.Win32; using Core; namespace TrustedUninstaller.Shared { [DefaultEvent("Exited"), DefaultProperty("StartInfo"), HostProtection(SharedState = true, Synchronization = true, ExternalProcessMgmt = true, SelfAffectingProcessMgmt = true)] public static class AugmentedProcess { public class Process : Component { public enum CreateType { UserToken, RawToken } // // FIELDS // bool haveProcessId; int processId; bool haveProcessHandle; SafeProcessHandle m_processHandle; bool isRemoteMachine; string machineName; ProcessInfo processInfo; Int32 m_processAccess; ProcessThreadCollection threads; ProcessModuleCollection modules; bool haveMainWindow; IntPtr mainWindowHandle; // no need to use SafeHandle for window string mainWindowTitle; bool haveWorkingSetLimits; bool haveProcessorAffinity; IntPtr processorAffinity; bool havePriorityClass; ProcessPriorityClass priorityClass; ProcessStartInfo startInfo; bool watchForExit; bool watchingForExit; EventHandler onExited; bool exited; int exitCode; bool signaled; DateTime exitTime; bool haveExitTime; bool responding; bool haveResponding; bool priorityBoostEnabled; bool havePriorityBoostEnabled; bool raisedOnExited; bool expandEnvironmentVariables; RegisteredWaitHandle registeredWaitHandle; WaitHandle waitHandle; ISynchronizeInvoke synchronizingObject; StreamReader standardOutput; StreamWriter standardInput; StreamReader standardError; OperatingSystem operatingSystem; bool disposed; static object s_CreateProcessLock = new object(); // This enum defines the operation mode for redirected process stream. // We don't support switching between synchronous mode and asynchronous mode. private enum StreamReadMode { undefined, syncMode, asyncMode } StreamReadMode outputStreamReadMode; StreamReadMode errorStreamReadMode; public event DataReceivedEventHandler OutputDataReceived; public event DataReceivedEventHandler ErrorDataReceived; // Abstract the stream details internal AsyncStreamReader output; internal AsyncStreamReader error; internal bool pendingOutputRead; internal bool pendingErrorRead; internal static TraceSwitch processTracing = null; public Process() { this.machineName = "."; this.outputStreamReadMode = StreamReadMode.undefined; this.errorStreamReadMode = StreamReadMode.undefined; this.m_processAccess = NativeMethods.PROCESS_ALL_ACCESS; } [ResourceExposure(ResourceScope.Machine)] Process(string machineName, bool isRemoteMachine, int processId, ProcessInfo processInfo) : base() { this.processInfo = processInfo; this.machineName = machineName; this.isRemoteMachine = isRemoteMachine; this.processId = processId; this.haveProcessId = true; this.outputStreamReadMode = StreamReadMode.undefined; this.errorStreamReadMode = StreamReadMode.undefined; this.m_processAccess = NativeMethods.PROCESS_ALL_ACCESS; } // // PROPERTIES // bool Associated { get { return haveProcessId || haveProcessHandle; } } public string ProcessName { get { this.EnsureState(Process.State.HaveProcessInfo); return this.processInfo.processName; } } public int ExitCode { get { EnsureState(State.Exited); return exitCode; } } public bool HasExited { get { if (!exited) { EnsureState(State.Associated); SafeProcessHandle handle = null; try { handle = GetProcessHandle(NativeMethods.PROCESS_QUERY_INFORMATION | NativeMethods.SYNCHRONIZE, false); if (handle.IsInvalid) { exited = true; } else { int exitCode; // Although this is the wrong way to check whether the process has exited, // it was historically the way we checked for it, and a lot of code then took a dependency on // the fact that this would always be set before the pipes were closed, so they would read // the exit code out after calling ReadToEnd() or standard output or standard error. In order // to allow 259 to function as a valid exit code and to break as few people as possible that // took the ReadToEnd dependency, we check for an exit code before doing the more correct // check to see if we have been signalled. if (NativeMethods.GetExitCodeProcess(handle, out exitCode) && exitCode != NativeMethods.STILL_ACTIVE) { this.exited = true; this.exitCode = exitCode; } else { // The best check for exit is that the kernel process object handle is invalid, // or that it is valid and signaled. Checking if the exit code != STILL_ACTIVE // does not guarantee the process is closed, // since some process could return an actual STILL_ACTIVE exit code (259). if (!signaled) // if we just came from WaitForExit, don't repeat { ProcessWaitHandle wh = null; try { wh = new ProcessWaitHandle(handle); this.signaled = wh.WaitOne(0, false); } finally { if (wh != null) wh.Close(); } } if (signaled) { if (!NativeMethods.GetExitCodeProcess(handle, out exitCode)) throw new Win32Exception(); this.exited = true; this.exitCode = exitCode; } } } } finally { ReleaseProcessHandle(handle); } if (exited) { RaiseOnExited(); } } return exited; } } public IntPtr Handle { [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] get { EnsureState(State.Associated); return OpenProcessHandle(this.m_processAccess).DangerousGetHandle(); } } [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public SafeProcessHandle SafeHandle { get { EnsureState(State.Associated); return OpenProcessHandle(this.m_processAccess); } } public int Id { get { EnsureState(State.HaveId); return processId; } } public string MachineName { get { EnsureState(State.Associated); return machineName; } } [System.Runtime.InteropServices.ComVisible(false)] public long NonpagedSystemMemorySize64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.poolNonpagedBytes; } } [System.Runtime.InteropServices.ComVisible(false)] public long PagedMemorySize64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.pageFileBytes; } } [System.Runtime.InteropServices.ComVisible(false)] public long PagedSystemMemorySize64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.poolPagedBytes; } } [System.Runtime.InteropServices.ComVisible(false)] public long PeakPagedMemorySize64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.pageFileBytesPeak; } } [System.Runtime.InteropServices.ComVisible(false)] public long PeakWorkingSet64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.workingSetPeak; } } [System.Runtime.InteropServices.ComVisible(false)] public long PeakVirtualMemorySize64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.virtualBytesPeak; } } private OperatingSystem OperatingSystem { get { if (operatingSystem == null) { operatingSystem = Environment.OSVersion; } return operatingSystem; } } [System.Runtime.InteropServices.ComVisible(false)] public long PrivateMemorySize64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.privateBytes; } } public int SessionId { get { EnsureState(State.HaveNtProcessInfo); return processInfo.sessionId; } } public ProcessStartInfo StartInfo { get { return startInfo; } [ResourceExposure(ResourceScope.Machine)] set { if (value == null) { throw new ArgumentNullException("value"); } startInfo = value; } } public bool ExpandEnvironmentVariables { get { return expandEnvironmentVariables; } set { expandEnvironmentVariables = value; } } public ISynchronizeInvoke SynchronizingObject { get { if (this.synchronizingObject == null && DesignMode) { IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost)); if (host != null) { object baseComponent = host.RootComponent; if (baseComponent != null && baseComponent is ISynchronizeInvoke) this.synchronizingObject = (ISynchronizeInvoke)baseComponent; } } return this.synchronizingObject; } set { this.synchronizingObject = value; } } [System.Runtime.InteropServices.ComVisible(false)] public long VirtualMemorySize64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.virtualBytes; } } public bool EnableRaisingEvents { get { return watchForExit; } set { if (value != watchForExit) { if (Associated) { if (value) { OpenProcessHandle(); EnsureWatchingForExit(); } else { StopWatchingForExit(); } } watchForExit = value; } } } public StreamWriter StandardInput { get { if (standardInput == null) { throw new InvalidOperationException("CantGetStandardIn"); } return standardInput; } } public StreamReader StandardOutput { get { if (standardOutput == null) { throw new InvalidOperationException("CantGetStandardOut"); } if (outputStreamReadMode == StreamReadMode.undefined) { outputStreamReadMode = StreamReadMode.syncMode; } else if (outputStreamReadMode != StreamReadMode.syncMode) { throw new InvalidOperationException("CantMixSyncAsyncOperation"); } return standardOutput; } } public StreamReader StandardError { get { if (standardError == null) { throw new InvalidOperationException("CantGetStandardError"); } if (errorStreamReadMode == StreamReadMode.undefined) { errorStreamReadMode = StreamReadMode.syncMode; } else if (errorStreamReadMode != StreamReadMode.syncMode) { throw new InvalidOperationException("CantMixSyncAsyncOperation"); } return standardError; } } public int WorkingSet { get { EnsureState(State.HaveNtProcessInfo); return unchecked((int)processInfo.workingSet); } } [System.Runtime.InteropServices.ComVisible(false)] public long WorkingSet64 { get { EnsureState(State.HaveNtProcessInfo); return processInfo.workingSet; } } public event EventHandler Exited { add { onExited += value; } remove { onExited -= value; } } /// /// Release the temporary handle we used to get process information. /// If we used the process handle stored in the process object (we have all access to the handle,) don't release it. /// /// void ReleaseProcessHandle(SafeProcessHandle handle) { if (handle == null) { return; } if (haveProcessHandle && handle == m_processHandle) { return; } handle.Close(); } /// /// This is called from the threadpool when a proces exits. /// /// private void CompletionCallback(object context, bool wasSignaled) { StopWatchingForExit(); RaiseOnExited(); } /// /// /// /// Free any resources associated with this component. /// /// protected override void Dispose(bool disposing) { if (!disposed) { if (disposing) { //Dispose managed and unmanaged resources Close(); } this.disposed = true; base.Dispose(disposing); } } /// /// /// Frees any resources associated with this component. /// /// public void Close() { if (Associated) { if (haveProcessHandle) { StopWatchingForExit(); m_processHandle.Close(); m_processHandle = null; haveProcessHandle = false; } haveProcessId = false; isRemoteMachine = false; machineName = "."; raisedOnExited = false; //Don't call close on the Readers and writers //since they might be referenced by somebody else while the //process is still alive but this method called. standardOutput = null; standardInput = null; standardError = null; output = null; error = null; Refresh(); } } /// /// Helper method for checking preconditions when accessing properties. /// /// [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] void EnsureState(State state) { if ((state & State.Associated) != (State)0) if (!Associated) throw new InvalidOperationException("NoAssociatedProcess"); if ((state & State.IsLocal) != (State)0 && isRemoteMachine) { throw new NotSupportedException("NotSupportedRemote"); } if ((state & Process.State.HaveProcessInfo) != (Process.State) 0 && this.processInfo == null) { if ((state & Process.State.HaveId) == (Process.State) 0) this.EnsureState(Process.State.HaveId); this.processInfo = GetProcessInfo(this.processId, this.machineName); if (this.processInfo == null) throw new InvalidOperationException("NoProcessInfo"); } if ((state & State.Exited) != (State)0) { if (!HasExited) { throw new InvalidOperationException("WaitTillExit"); } if (!haveProcessHandle) { throw new InvalidOperationException("NoProcessHandle"); } } } void EnsureWatchingForExit() { if (!watchingForExit) { lock (this) { if (!watchingForExit) { watchingForExit = true; try { this.waitHandle = new ProcessWaitHandle(m_processHandle); this.registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(this.waitHandle, new WaitOrTimerCallback(this.CompletionCallback), null, -1, true); } catch { watchingForExit = false; throw; } } } } } protected void OnExited() { EventHandler exited = onExited; if (exited != null) { if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired) this.SynchronizingObject.BeginInvoke(exited, new object[] { this, EventArgs.Empty }); else exited(this, EventArgs.Empty); } } [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] SafeProcessHandle GetProcessHandle(int access, bool throwIfExited) { if (haveProcessHandle) { if (throwIfExited) { // Since haveProcessHandle is true, we know we have the process handle // open with at least SYNCHRONIZE access, so we can wait on it with // zero timeout to see if the process has exited. ProcessWaitHandle waitHandle = null; try { waitHandle = new ProcessWaitHandle(m_processHandle); if (waitHandle.WaitOne(0, false)) { if (haveProcessId) throw new InvalidOperationException("Process has exited: " + processId); else throw new InvalidOperationException("ProcessHasExitedNoId"); } } finally { if (waitHandle != null) { waitHandle.Close(); } } } return m_processHandle; } else { throw new Exception("(AME) Process handle not available."); } } /// /// Gets a short-term handle to the process, with the given access. If a handle exists, /// then it is reused. If the process has exited, it throws an exception. /// /// SafeProcessHandle GetProcessHandle(int access) { return GetProcessHandle(access, true); } /// /// Opens a long-term handle to the process, with all access. If a handle exists, /// then it is reused. If the process has exited, it throws an exception. /// /// SafeProcessHandle OpenProcessHandle() { return OpenProcessHandle(NativeMethods.PROCESS_ALL_ACCESS); } SafeProcessHandle OpenProcessHandle(Int32 access) { if (!haveProcessHandle) { //Cannot open a new process handle if the object has been disposed, since finalization has been suppressed. if (this.disposed) { throw new ObjectDisposedException(GetType().Name); } SetProcessHandle(GetProcessHandle(access)); } return m_processHandle; } /// /// Raise the Exited event, but make sure we don't do it more than once. /// /// void RaiseOnExited() { if (!raisedOnExited) { lock (this) { if (!raisedOnExited) { raisedOnExited = true; OnExited(); } } } } /// /// /// Discards any information about the associated process /// that has been cached inside the process component. After is called, the /// first request for information for each property causes the process component /// to obtain a new value from the associated process. /// /// public void Refresh() { processInfo = null; threads = null; modules = null; mainWindowTitle = null; exited = false; signaled = false; haveMainWindow = false; haveWorkingSetLimits = false; haveProcessorAffinity = false; havePriorityClass = false; haveExitTime = false; haveResponding = false; havePriorityBoostEnabled = false; } /// /// Helper to associate a process handle with this component. /// /// void SetProcessHandle(SafeProcessHandle processHandle) { this.m_processHandle = processHandle; this.haveProcessHandle = true; if (watchForExit) { EnsureWatchingForExit(); } } /// /// Helper to associate a process id with this component. /// /// [ResourceExposure(ResourceScope.Machine)] void SetProcessId(int processId) { this.processId = processId; this.haveProcessId = true; } /// /// /// Starts a process specified by the property of this /// component and associates it with the /// . If a process resource is reused /// rather than started, the reused process is associated with this /// component. /// /// [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] public bool Start(CreateType type, ref Win32.TokensEx.SafeTokenHandle token) { Close(); ProcessStartInfo startInfo = StartInfo; if (startInfo.FileName.Length == 0) throw new InvalidOperationException("FileNameMissing"); return StartWithCreateProcess(startInfo, type, ref token); } [ResourceExposure(ResourceScope.Process)] [ResourceConsumption(ResourceScope.Process)] private static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, NativeMethods.SECURITY_ATTRIBUTES lpPipeAttributes, int nSize) { bool ret = NativeMethods.CreatePipe(out hReadPipe, out hWritePipe, lpPipeAttributes, nSize); if (!ret || hReadPipe.IsInvalid || hWritePipe.IsInvalid) { throw new Win32Exception(); } } // Using synchronous Anonymous pipes for process input/output redirection means we would end up // wasting a worker threadpool thread per pipe instance. Overlapped pipe IO is desirable, since // it will take advantage of the NT IO completion port infrastructure. But we can't really use // Overlapped I/O for process input/output as it would break Console apps (managed Console class // methods such as WriteLine as well as native CRT functions like printf) which are making an // assumption that the console standard handles (obtained via GetStdHandle()) are opened // for synchronous I/O and hence they can work fine with ReadFile/WriteFile synchrnously! [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] private void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs) { NativeMethods.SECURITY_ATTRIBUTES securityAttributesParent = new NativeMethods.SECURITY_ATTRIBUTES(); securityAttributesParent.bInheritHandle = true; SafeFileHandle hTmp = null; try { if (parentInputs) { CreatePipeWithSecurityAttributes(out childHandle, out hTmp, securityAttributesParent, 0); } else { CreatePipeWithSecurityAttributes(out hTmp, out childHandle, securityAttributesParent, 0); } // Duplicate the parent handle to be non-inheritable so that the child process // doesn't have access. This is done for correctness sake, exact reason is unclear. // One potential theory is that child process can do something brain dead like // closing the parent end of the pipe and there by getting into a blocking situation // as parent will not be draining the pipe at the other end anymore. if (!NativeMethods.DuplicateHandle(new HandleRef(this, NativeMethods.GetCurrentProcess()), hTmp, new HandleRef(this, NativeMethods.GetCurrentProcess()), out parentHandle, 0, false, NativeMethods.DUPLICATE_SAME_ACCESS)) { throw new Win32Exception(); } } finally { if (hTmp != null && !hTmp.IsInvalid) { hTmp.Close(); } } } private static StringBuilder BuildCommandLine(string executableFileName, string arguments) { // Construct a StringBuilder with the appropriate command line // to pass to CreateProcess. If the filename isn't already // in quotes, we quote it here. This prevents some security // problems (it specifies exactly which part of the string // is the file to execute). StringBuilder commandLine = new StringBuilder(); string fileName = executableFileName.Trim(); bool fileNameIsQuoted = (fileName.StartsWith("\"", StringComparison.Ordinal) && fileName.EndsWith("\"", StringComparison.Ordinal)); if (!fileNameIsQuoted) { commandLine.Append("\""); } commandLine.Append(fileName); if (!fileNameIsQuoted) { commandLine.Append("\""); } if (!String.IsNullOrEmpty(arguments)) { commandLine.Append(" "); commandLine.Append(arguments); } return commandLine; } [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] private bool StartWithCreateProcess(ProcessStartInfo startInfo, CreateType type, ref Win32.TokensEx.SafeTokenHandle token) { // See knowledge base article Q190351 for an explanation of the following code. Noteworthy tricky points: // * The handles are duplicated as non-inheritable before they are passed to CreateProcess so // that the child process can not close them // * CreateProcess allows you to redirect all or none of the standard IO handles, so we use // GetStdHandle for the handles that are not being redirected //Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed. if (this.disposed) { throw new ObjectDisposedException(GetType().Name); } StringBuilder commandLine = BuildCommandLine(startInfo.FileName, startInfo.Arguments); NativeMethods.STARTUPINFO startupInfo = new NativeMethods.STARTUPINFO(); NativeMethods.PROCESS_INFORMATION processInfo = new NativeMethods.PROCESS_INFORMATION(); SafeProcessHandle procSH = new SafeProcessHandle(); SafeThreadHandle threadSH = new SafeThreadHandle(); bool retVal; int errorCode = 0; // handles used in parent process SafeFileHandle standardInputWritePipeHandle = null; SafeFileHandle standardOutputReadPipeHandle = null; SafeFileHandle standardErrorReadPipeHandle = null; IntPtr environmentPtr = (IntPtr)0; //GCHandle environmentHandle = new GCHandle(); lock (s_CreateProcessLock) { try { // set up the streams if (startInfo.CreateNoWindow && (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError)) { if (startInfo.StandardOutputEncoding != null && !startInfo.RedirectStandardOutput) { throw new InvalidOperationException("StandardOutputEncodingNotAllowed"); } if (startInfo.StandardErrorEncoding != null && !startInfo.RedirectStandardError) { throw new InvalidOperationException("StandardErrorEncodingNotAllowed"); } if (startInfo.RedirectStandardInput) { CreatePipe(out standardInputWritePipeHandle, out startupInfo.hStdInput, true); } else { startupInfo.hStdInput = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_INPUT_HANDLE), false); } if (startInfo.RedirectStandardOutput) { CreatePipe(out standardOutputReadPipeHandle, out startupInfo.hStdOutput, false); } else { startupInfo.hStdOutput = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE), false); } if (startInfo.RedirectStandardError) { CreatePipe(out standardErrorReadPipeHandle, out startupInfo.hStdError, false); } else { startupInfo.hStdError = new SafeFileHandle(NativeMethods.GetStdHandle(NativeMethods.STD_ERROR_HANDLE), false); } startupInfo.dwFlags = NativeMethods.STARTF_USESTDHANDLES; } // set up the creation flags paramater int creationFlags = 0; if (startInfo.CreateNoWindow) creationFlags |= NativeMethods.CREATE_NO_WINDOW; // set up the environment block parameterhttps://www.beyondtrust.com/assets/documents/BeyondTrust-Microsoft-Vulnerabilities-Report-2021.pdf //if (startInfo.environmentVariables != null) if (true) { creationFlags |= NativeMethods.CREATE_UNICODE_ENVIRONMENT; //byte[] environmentBytes = EnvironmentBlock.ToByteArray(startInfo.environmentVariables, true); //environmentHandle = GCHandle.Alloc(environmentBytes, GCHandleType.Pinned); //environmentPtr = environmentHandle.AddrOfPinnedObject(); Win32.Process.CreateEnvironmentBlock(out environmentPtr, token, true); } /* if (ExpandEnvironmentVariables && startInfo.Arguments.Contains("%")) { Environment.ExpandEnvironmentVariables() var envVars = new Dictionary(StringComparer.OrdinalIgnoreCase); IntPtr next = environmentPtr; while (Marshal.ReadByte(next) != 0) { var str = Marshal.PtrToStringUni(next); // skip first character because windows allows env vars to begin with equal sign var splitPoint = str.IndexOf('=', 1); var envVarName = str.Substring(0, splitPoint); var envVarVal = str.Substring(splitPoint + 1); envVars.Add(envVarName, envVarVal); next = (IntPtr)((Int64)next + (str.Length * 2) + 2); } return envVars; } */ if (!startInfo.CreateNoWindow) { creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_DEFAULT_ERROR_MODE; creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_NEW_CONSOLE; creationFlags |= (int)Win32.Process.ProcessCreationFlags.CREATE_NEW_PROCESS_GROUP; //startupInfo.lpDesktop = "Winsta0\\Default"; } string workingDirectory = startInfo.WorkingDirectory; if (workingDirectory == string.Empty) workingDirectory = Environment.CurrentDirectory; RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { retVal = false; if (type == CreateType.UserToken) { if (startInfo.UseSession0) { var sessionId = 0; Win32.Tokens.SetTokenInformation(token, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, sizeof(int)); } retVal = NativeMethods.CreateProcessAsUser(token, null, // we don't need this since all the info is in commandLine commandLine, // pointer to the command line string null, // pointer to process security attributes, we don't need to inheriat the handle null, // pointer to thread security attributes true, // handle inheritance flag creationFlags, // creation flags environmentPtr, // pointer to new environment block workingDirectory, // pointer to current directory name startupInfo, // pointer to STARTUPINFO processInfo // pointer to PROCESS_INFORMATION ); } else if (type == CreateType.RawToken) { if (startInfo.UseSession0) { var sessionId = 0; Win32.Tokens.SetTokenInformation(token, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, sizeof(int)); } retVal = NativeMethods.CreateProcessWithToken(token, NativeMethods.LogonFlags.LOGON_WITH_PROFILE, null, // we don't need this since all the info is in commandLine commandLine, // pointer to the command line string creationFlags, // creation flags environmentPtr, // pointer to new environment block workingDirectory, // pointer to current directory name startupInfo, // pointer to STARTUPINFO processInfo // pointer to PROCESS_INFORMATION ); } /* retVal = NativeMethods.CreateProcess(null, // we don't need this since all the info is in commandLine commandLine, // pointer to the command line string null, // pointer to process security attributes, we don't need to inheriat the handle null, // pointer to thread security attributes true, // handle inheritance flag creationFlags, // creation flags environmentPtr, // pointer to new environment block workingDirectory, // pointer to current directory name startupInfo, // pointer to STARTUPINFO processInfo // pointer to PROCESS_INFORMATION ); */ if (!retVal) errorCode = Marshal.GetLastWin32Error(); if (processInfo.hProcess != (IntPtr)0 && processInfo.hProcess != (IntPtr)NativeMethods.INVALID_HANDLE_VALUE) procSH.InitialSetHandle(processInfo.hProcess); if (processInfo.hThread != (IntPtr)0 && processInfo.hThread != (IntPtr)NativeMethods.INVALID_HANDLE_VALUE) threadSH.InitialSetHandle(processInfo.hThread); } if (!retVal) { if (errorCode == NativeMethods.ERROR_BAD_EXE_FORMAT || errorCode == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH) { throw new Win32Exception(errorCode, "InvalidApplication"); } throw new Win32Exception(errorCode); } } finally { // free environment block //if (environmentHandle.IsAllocated) //{ // environmentHandle.Free(); //} Win32.Process.DestroyEnvironmentBlock(environmentPtr); startupInfo.Dispose(); } } if (startInfo.RedirectStandardInput) { standardInput = new StreamWriter(new FileStream(standardInputWritePipeHandle, FileAccess.Write, 4096, false), Console.InputEncoding, 4096); standardInput.AutoFlush = true; } if (startInfo.RedirectStandardOutput) { Encoding enc = (startInfo.StandardOutputEncoding != null) ? startInfo.StandardOutputEncoding : Console.OutputEncoding; standardOutput = new StreamReader(new FileStream(standardOutputReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096); } if (startInfo.RedirectStandardError) { Encoding enc = (startInfo.StandardErrorEncoding != null) ? startInfo.StandardErrorEncoding : Console.OutputEncoding; standardError = new StreamReader(new FileStream(standardErrorReadPipeHandle, FileAccess.Read, 4096, false), enc, true, 4096); } bool ret = false; if (!procSH.IsInvalid) { SetProcessHandle(procSH); SetProcessId(processInfo.dwProcessId); threadSH.Close(); ret = true; } return ret; } /* private static string ExpandEnvironmentVariables(string name, Dictionary environment) { switch (name) { case null: case "": return name; default: environment = new Dictionary(StringComparer.OrdinalIgnoreCase) { {"Test", "Example"}, }; StringBuilder result = new StringBuilder(); int index = name.IndexOf('%'); if (index > 0) result.Append(name.Substring(index)); int lastValidIndex = -1; while (index != -1) { lastValidIndex = index; var end = name.IndexOf('%', index + 1); if (end == -1) { result.Append('%'); break; } // Double % escape if (end == index + 1) { index = name.IndexOf('%', index + 2); result.Append(index == -1 ? "%" : "%" + name.Substring(end + 1, index - (end + 1))); continue; } lastValidIndex = end; if (environment.TryGetValue(name.Substring(index + 1, end - (index + 1)), out string varValue)) result.Append(varValue); index = name.IndexOf('%', end + 1); if (index != -1) { result.Append(name.Substring(end + 1, index - (end + 1))); } } result.Append(lastValidIndex != -1 ? name.Substring(index + 1, name.Length - (index + 1)) : name); return result.ToString(); } } */ [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] private bool StartWithShellExecuteEx(ProcessStartInfo startInfo) { //Cannot start a new process and store its handle if the object has been disposed, since finalization has been suppressed. if (this.disposed) throw new ObjectDisposedException(GetType().Name); if (!String.IsNullOrEmpty(startInfo.UserName) || (startInfo.Password != null)) { throw new InvalidOperationException("CantStartAsUser"); } if (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError) { throw new InvalidOperationException("CantRedirectStreams"); } if (startInfo.StandardErrorEncoding != null) { throw new InvalidOperationException("StandardErrorEncodingNotAllowed"); } if (startInfo.StandardOutputEncoding != null) { throw new InvalidOperationException("StandardOutputEncodingNotAllowed"); } // can't set env vars with ShellExecuteEx... if (startInfo.environmentVariables != null) { throw new InvalidOperationException("CantUseEnvVars"); } NativeMethods.ShellExecuteInfo shellExecuteInfo = new NativeMethods.ShellExecuteInfo(); shellExecuteInfo.fMask = NativeMethods.SEE_MASK_NOCLOSEPROCESS; if (startInfo.ErrorDialog) { shellExecuteInfo.hwnd = startInfo.ErrorDialogParentHandle; } else { shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_NO_UI; } switch (startInfo.WindowStyle) { case ProcessWindowStyle.Hidden: shellExecuteInfo.nShow = NativeMethods.SW_HIDE; break; case ProcessWindowStyle.Minimized: shellExecuteInfo.nShow = NativeMethods.SW_SHOWMINIMIZED; break; case ProcessWindowStyle.Maximized: shellExecuteInfo.nShow = NativeMethods.SW_SHOWMAXIMIZED; break; default: shellExecuteInfo.nShow = NativeMethods.SW_SHOWNORMAL; break; } try { if (startInfo.FileName.Length != 0) shellExecuteInfo.lpFile = Marshal.StringToHGlobalAuto(startInfo.FileName); if (startInfo.Verb.Length != 0) shellExecuteInfo.lpVerb = Marshal.StringToHGlobalAuto(startInfo.Verb); if (startInfo.Arguments.Length != 0) shellExecuteInfo.lpParameters = Marshal.StringToHGlobalAuto(startInfo.Arguments); if (startInfo.WorkingDirectory.Length != 0) shellExecuteInfo.lpDirectory = Marshal.StringToHGlobalAuto(startInfo.WorkingDirectory); shellExecuteInfo.fMask |= NativeMethods.SEE_MASK_FLAG_DDEWAIT; ShellExecuteHelper executeHelper = new ShellExecuteHelper(shellExecuteInfo); if (!executeHelper.ShellExecuteOnSTAThread()) { int error = executeHelper.ErrorCode; if (error == 0) { switch ((long)shellExecuteInfo.hInstApp) { case NativeMethods.SE_ERR_FNF: error = NativeMethods.ERROR_FILE_NOT_FOUND; break; case NativeMethods.SE_ERR_PNF: error = NativeMethods.ERROR_PATH_NOT_FOUND; break; case NativeMethods.SE_ERR_ACCESSDENIED: error = NativeMethods.ERROR_ACCESS_DENIED; break; case NativeMethods.SE_ERR_OOM: error = NativeMethods.ERROR_NOT_ENOUGH_MEMORY; break; case NativeMethods.SE_ERR_DDEFAIL: case NativeMethods.SE_ERR_DDEBUSY: case NativeMethods.SE_ERR_DDETIMEOUT: error = NativeMethods.ERROR_DDE_FAIL; break; case NativeMethods.SE_ERR_SHARE: error = NativeMethods.ERROR_SHARING_VIOLATION; break; case NativeMethods.SE_ERR_NOASSOC: error = NativeMethods.ERROR_NO_ASSOCIATION; break; case NativeMethods.SE_ERR_DLLNOTFOUND: error = NativeMethods.ERROR_DLL_NOT_FOUND; break; default: error = (int)shellExecuteInfo.hInstApp; break; } } if (error == NativeMethods.ERROR_BAD_EXE_FORMAT || error == NativeMethods.ERROR_EXE_MACHINE_TYPE_MISMATCH) { throw new Win32Exception(error, "InvalidApplication"); } throw new Win32Exception(error); } } finally { if (shellExecuteInfo.lpFile != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpFile); if (shellExecuteInfo.lpVerb != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpVerb); if (shellExecuteInfo.lpParameters != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpParameters); if (shellExecuteInfo.lpDirectory != (IntPtr)0) Marshal.FreeHGlobal(shellExecuteInfo.lpDirectory); } if (shellExecuteInfo.hProcess != (IntPtr)0) { SafeProcessHandle handle = new SafeProcessHandle(shellExecuteInfo.hProcess); SetProcessHandle(handle); return true; } return false; } /// /// /// Starts a process resource specified by the process start /// information passed in, for example the file name of the process to start. /// Associates the process resource with a new /// component. /// /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] public static Process Start(CreateType type, ProcessStartInfo startInfo, Win32.TokensEx.SafeTokenHandle token) { Process process = new Process(); if (startInfo == null) throw new ArgumentNullException("startInfo"); process.StartInfo = startInfo; if (process.Start(type, ref token)) { return process; } return null; } /// /// /// Stops the /// associated process immediately. /// /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] public void Kill() { SafeProcessHandle handle = null; try { handle = GetProcessHandle(NativeMethods.PROCESS_TERMINATE); if (!NativeMethods.TerminateProcess(handle, -1)) throw new Win32Exception(); } finally { ReleaseProcessHandle(handle); } } /// /// Make sure we are not watching for process exit. /// /// void StopWatchingForExit() { if (watchingForExit) { lock (this) { if (watchingForExit) { watchingForExit = false; registeredWaitHandle.Unregister(null); waitHandle.Close(); waitHandle = null; registeredWaitHandle = null; } } } } /// /// /// Instructs the component to wait the specified number of milliseconds for the associated process to exit. /// /// public bool WaitForExit(int milliseconds) { SafeProcessHandle handle = null; bool exited; ProcessWaitHandle processWaitHandle = null; try { handle = GetProcessHandle(NativeMethods.SYNCHRONIZE, false); if (handle.IsInvalid) { exited = true; } else { processWaitHandle = new ProcessWaitHandle(handle); if (processWaitHandle.WaitOne(milliseconds, false)) { exited = true; signaled = true; } else { exited = false; signaled = false; } } } finally { if (processWaitHandle != null) { processWaitHandle.Close(); } // If we have a hard timeout, we cannot wait for the streams if (output != null && milliseconds == -1) { output.WaitUtilEOF(); } if (error != null && milliseconds == -1) { error.WaitUtilEOF(); } ReleaseProcessHandle(handle); } if (exited && watchForExit) { RaiseOnExited(); } return exited; } /// /// /// Instructs the component to wait /// indefinitely for the associated process to exit. /// /// public void WaitForExit() { WaitForExit(-1); } /// /// /// Causes the component to wait the /// specified number of milliseconds for the associated process to enter an /// idle state. /// This is only applicable for processes with a user interface, /// therefore a message loop. /// /// public bool WaitForInputIdle(int milliseconds) { SafeProcessHandle handle = null; bool idle; try { handle = GetProcessHandle(NativeMethods.SYNCHRONIZE | NativeMethods.PROCESS_QUERY_INFORMATION); int ret = NativeMethods.WaitForInputIdle(handle, milliseconds); switch (ret) { case NativeMethods.WAIT_OBJECT_0: idle = true; break; case NativeMethods.WAIT_TIMEOUT: idle = false; break; case NativeMethods.WAIT_FAILED: default: throw new InvalidOperationException("InputIdleUnkownError"); } } finally { ReleaseProcessHandle(handle); } return idle; } /// /// /// Instructs the component to wait /// indefinitely for the associated process to enter an idle state. This /// is only applicable for processes with a user interface, therefore a message loop. /// /// public bool WaitForInputIdle() { return WaitForInputIdle(Int32.MaxValue); } // Support for working asynchronously with streams /// /// /// Instructs the component to start /// reading the StandardOutput stream asynchronously. The user can register a callback /// that will be called when a line of data terminated by \n,\r or \r\n is reached, or the end of stream is reached /// then the remaining information is returned. The user can add an event handler to OutputDataReceived. /// /// [System.Runtime.InteropServices.ComVisible(false)] public void BeginOutputReadLine() { if (outputStreamReadMode == StreamReadMode.undefined) { outputStreamReadMode = StreamReadMode.asyncMode; } else if (outputStreamReadMode != StreamReadMode.asyncMode) { throw new InvalidOperationException("CantMixSyncAsyncOperation"); } if (pendingOutputRead) throw new InvalidOperationException("PendingAsyncOperation"); pendingOutputRead = true; // We can't detect if there's a pending sychronous read, tream also doesn't. if (output == null) { if (standardOutput == null) { throw new InvalidOperationException("CantGetStandardOut"); } Stream s = standardOutput.BaseStream; output = new AsyncStreamReader(this, s, new UserCallBack(this.OutputReadNotifyUser), standardOutput.CurrentEncoding); } output.BeginReadLine(); } /// /// /// Instructs the component to start /// reading the StandardError stream asynchronously. The user can register a callback /// that will be called when a line of data terminated by \n,\r or \r\n is reached, or the end of stream is reached /// then the remaining information is returned. The user can add an event handler to ErrorDataReceived. /// /// [System.Runtime.InteropServices.ComVisible(false)] public void BeginErrorReadLine() { if (errorStreamReadMode == StreamReadMode.undefined) { errorStreamReadMode = StreamReadMode.asyncMode; } else if (errorStreamReadMode != StreamReadMode.asyncMode) { throw new InvalidOperationException("CantMixSyncAsyncOperation"); } if (pendingErrorRead) { throw new InvalidOperationException("PendingAsyncOperation"); } pendingErrorRead = true; // We can't detect if there's a pending sychronous read, stream also doesn't. if (error == null) { if (standardError == null) { throw new InvalidOperationException("CantGetStandardError"); } Stream s = standardError.BaseStream; error = new AsyncStreamReader(this, s, new UserCallBack(this.ErrorReadNotifyUser), standardError.CurrentEncoding); } error.BeginReadLine(); } /// /// /// Instructs the component to cancel the asynchronous operation /// specified by BeginOutputReadLine(). /// /// [System.Runtime.InteropServices.ComVisible(false)] public void CancelOutputRead() { if (output != null) { output.CancelOperation(); } else { throw new InvalidOperationException("NoAsyncOperation"); } pendingOutputRead = false; } /// /// /// Instructs the component to cancel the asynchronous operation /// specified by BeginErrorReadLine(). /// /// [System.Runtime.InteropServices.ComVisible(false)] public void CancelErrorRead() { if (error != null) { error.CancelOperation(); } else { throw new InvalidOperationException("No async operation."); } pendingErrorRead = false; } internal void OutputReadNotifyUser(String data) { // To avoid ---- between remove handler and raising the event DataReceivedEventHandler outputDataReceived = OutputDataReceived; if (outputDataReceived != null) { DataReceivedEventArgs e = new DataReceivedEventArgs(data); if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) { SynchronizingObject.Invoke(outputDataReceived, new object[] { this, e }); } else { outputDataReceived(this, e); // Call back to user informing data is available. } } } internal void ErrorReadNotifyUser(String data) { // To avoid ---- between remove handler and raising the event DataReceivedEventHandler errorDataReceived = ErrorDataReceived; if (errorDataReceived != null) { DataReceivedEventArgs e = new DataReceivedEventArgs(data); if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) { SynchronizingObject.Invoke(errorDataReceived, new object[] { this, e }); } else { errorDataReceived(this, e); // Call back to user informing data is available. } } } /// /// A desired internal state. /// /// enum State { HaveId = 0x1, IsLocal = 0x2, IsNt = 0x4, HaveProcessInfo = 0x8, Exited = 0x10, Associated = 0x20, IsWin2k = 0x40, HaveNtProcessInfo = HaveProcessInfo | IsNt } } /// /// This data structure contains information about a process that is collected /// in bulk by querying the operating system. The reason to make this a separate /// structure from the process component is so that we can throw it away all at once /// when Refresh is called on the component. /// /// internal class ProcessInfo { public ArrayList threadInfoList = new ArrayList(); public int basePriority; public string processName; public int processId; public int handleCount; public long poolPagedBytes; public long poolNonpagedBytes; public long virtualBytes; public long virtualBytesPeak; public long workingSetPeak; public long workingSet; public long pageFileBytesPeak; public long pageFileBytes; public long privateBytes; public int mainModuleId; // used only for win9x - id is only for use with CreateToolHelp32 public int sessionId; } /// /// This data structure contains information about a thread in a process that /// is collected in bulk by querying the operating system. The reason to /// make this a separate structure from the ProcessThread component is so that we /// can throw it away all at once when Refresh is called on the component. /// /// internal class ThreadInfo { public int threadId; public int processId; public int basePriority; public int currentPriority; public IntPtr startAddress; public System.Diagnostics.ThreadState threadState; public ThreadWaitReason threadWaitReason; } /// /// This data structure contains information about a module in a process that /// is collected in bulk by querying the operating system. The reason to /// make this a separate structure from the ProcessModule component is so that we /// can throw it away all at once when Refresh is called on the component. /// /// internal class ModuleInfo { public string baseName; public string fileName; public IntPtr baseOfDll; public IntPtr entryPoint; public int sizeOfImage; public int Id; // used only on win9x - for matching up with ProcessInfo.mainModuleId } internal static class EnvironmentBlock { public static byte[] ToByteArray(StringDictionary sd, bool unicode) { // get the keys string[] keys = new string[sd.Count]; byte[] envBlock = null; sd.Keys.CopyTo(keys, 0); // get the values string[] values = new string[sd.Count]; sd.Values.CopyTo(values, 0); // sort both by the keys // Windows 2000 requires the environment block to be sorted by the key // It will first converting the case the strings and do ordinal comparison. Array.Sort(keys, values, OrdinalCaseInsensitiveComparer.Default); // create a list of null terminated "key=val" strings StringBuilder stringBuff = new StringBuilder(); for (int i = 0; i < sd.Count; ++i) { stringBuff.Append(keys[i]); stringBuff.Append('='); stringBuff.Append(values[i]); stringBuff.Append('\0'); } // an extra null at the end indicates end of list. stringBuff.Append('\0'); if (unicode) { envBlock = Encoding.Unicode.GetBytes(stringBuff.ToString()); } else { envBlock = Encoding.Default.GetBytes(stringBuff.ToString()); if (envBlock.Length > UInt16.MaxValue) throw new InvalidOperationException("Environment block is too long."); } return envBlock; } } internal class OrdinalCaseInsensitiveComparer : IComparer { internal static readonly OrdinalCaseInsensitiveComparer Default = new OrdinalCaseInsensitiveComparer(); public int Compare(Object a, Object b) { String sa = a as String; String sb = b as String; if (sa != null && sb != null) { return String.Compare(sa, sb, StringComparison.OrdinalIgnoreCase); } return Comparer.Default.Compare(a, b); } } internal class ShellExecuteHelper { private NativeMethods.ShellExecuteInfo _executeInfo; private int _errorCode; private bool _succeeded; public ShellExecuteHelper(NativeMethods.ShellExecuteInfo executeInfo) { _executeInfo = executeInfo; } [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] public void ShellExecuteFunction() { if (!(_succeeded = NativeMethods.ShellExecuteEx(_executeInfo))) { _errorCode = Marshal.GetLastWin32Error(); } } public bool ShellExecuteOnSTAThread() { // // SHELL API ShellExecute() requires STA in order to work correctly. // If current thread is not a STA thread, we need to call ShellExecute on a new thread. // if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) { ThreadStart threadStart = new ThreadStart(this.ShellExecuteFunction); Thread executionThread = new Thread(threadStart); executionThread.SetApartmentState(ApartmentState.STA); executionThread.Start(); executionThread.Join(); } else { ShellExecuteFunction(); } return _succeeded; } public int ErrorCode { get { return _errorCode; } } } private static long[] CachedBuffer; private static int GetNewBufferSize(int existingBufferSize, int requiredSize) { if (requiredSize == 0) { int num = existingBufferSize * 2; return num >= existingBufferSize ? num : throw new OutOfMemoryException(); } int num1 = requiredSize + 10240; return num1 >= requiredSize ? num1 : throw new OutOfMemoryException(); } internal static ProcessInfo GetProcessInfo(int processId, string machineName) { ProcessInfo[] processInfos = GetProcessInfos((Predicate)(pid => pid == processId)); if (processInfos.Length == 1) return processInfos[0]; return (ProcessInfo)null; } internal static ProcessInfo[] GetProcessInfos(Predicate processIdFilter = null) { int returnedSize = 0; GCHandle gcHandle = new GCHandle(); int num = 131072; long[] numArray = Interlocked.Exchange(ref CachedBuffer, (long[])null); try { int error; do { if (numArray == null) numArray = new long[(num + 7) / 8]; else num = numArray.Length * 8; gcHandle = GCHandle.Alloc((object)numArray, GCHandleType.Pinned); error = NativeMethods.NtQuerySystemInformation(5, gcHandle.AddrOfPinnedObject(), num, out returnedSize); if (error == -1073741820) { if (gcHandle.IsAllocated) gcHandle.Free(); numArray = (long[])null; num = GetNewBufferSize(num, returnedSize); } } while (error == -1073741820); if (error < 0) throw new InvalidOperationException("CouldntGetProcessInfos", (Exception)new Win32Exception(error)); return GetProcessInfos(gcHandle.AddrOfPinnedObject(), processIdFilter); } finally { Interlocked.Exchange(ref CachedBuffer, numArray); if (gcHandle.IsAllocated) gcHandle.Free(); } } private static ProcessInfo[] GetProcessInfos(IntPtr dataPtr, Predicate processIdFilter) { Hashtable hashtable = new Hashtable(60); long num = 0; while (true) { IntPtr ptr1 = (IntPtr)((long)dataPtr + num); NativeMethods.SystemProcessInformation structure1 = new NativeMethods.SystemProcessInformation(); Marshal.PtrToStructure(ptr1, (object)structure1); int int32 = structure1.UniqueProcessId.ToInt32(); if (processIdFilter == null || processIdFilter(int32)) { ProcessInfo processInfo = new ProcessInfo(); processInfo.processId = int32; processInfo.handleCount = (int)structure1.HandleCount; processInfo.sessionId = (int)structure1.SessionId; processInfo.poolPagedBytes = (long)(ulong)structure1.QuotaPagedPoolUsage; processInfo.poolNonpagedBytes = (long)(ulong)structure1.QuotaNonPagedPoolUsage; processInfo.virtualBytes = (long)(ulong)structure1.VirtualSize; processInfo.virtualBytesPeak = (long)(ulong)structure1.PeakVirtualSize; processInfo.workingSetPeak = (long)(ulong)structure1.PeakWorkingSetSize; processInfo.workingSet = (long)(ulong)structure1.WorkingSetSize; processInfo.pageFileBytesPeak = (long)(ulong)structure1.PeakPagefileUsage; processInfo.pageFileBytes = (long)(ulong)structure1.PagefileUsage; processInfo.privateBytes = (long)(ulong)structure1.PrivatePageCount; processInfo.basePriority = structure1.BasePriority; if (structure1.NamePtr == IntPtr.Zero) { processInfo.processName = processInfo.processId != 4 ? (processInfo.processId != 0 ? processInfo.processId.ToString((IFormatProvider)CultureInfo.InvariantCulture) : "Idle") : "System"; } else { string str = GetProcessShortName(Marshal.PtrToStringUni(structure1.NamePtr, (int)structure1.NameLength / 2)); processInfo.processName = str; } hashtable[(object)processInfo.processId] = (object)processInfo; IntPtr ptr2 = (IntPtr)((long)ptr1 + (long)Marshal.SizeOf((object)structure1)); for (int index = 0; (long)index < (long)structure1.NumberOfThreads; ++index) { NativeMethods.SystemThreadInformation structure2 = new NativeMethods.SystemThreadInformation(); Marshal.PtrToStructure(ptr2, (object)structure2); processInfo.threadInfoList.Add((object)new ThreadInfo() { processId = (int)structure2.UniqueProcess, threadId = (int)structure2.UniqueThread, basePriority = structure2.BasePriority, currentPriority = structure2.Priority, startAddress = structure2.StartAddress, threadState = (System.Diagnostics.ThreadState)structure2.ThreadState, threadWaitReason = GetThreadWaitReason((int)structure2.WaitReason) }); ptr2 = (IntPtr)((long)ptr2 + (long)Marshal.SizeOf((object)structure2)); } } if (structure1.NextEntryOffset != 0U) num += (long)structure1.NextEntryOffset; else break; } ProcessInfo[] processInfos = new ProcessInfo[hashtable.Values.Count]; hashtable.Values.CopyTo((Array)processInfos, 0); return processInfos; } internal static ThreadWaitReason GetThreadWaitReason(int value) { switch (value) { case 0: case 7: return ThreadWaitReason.Executive; case 1: case 8: return ThreadWaitReason.FreePage; case 2: case 9: return ThreadWaitReason.PageIn; case 3: case 10: return ThreadWaitReason.SystemAllocation; case 4: case 11: return ThreadWaitReason.ExecutionDelay; case 5: case 12: return ThreadWaitReason.Suspended; case 6: case 13: return ThreadWaitReason.UserRequest; case 14: return ThreadWaitReason.EventPairHigh; case 15: return ThreadWaitReason.EventPairLow; case 16: return ThreadWaitReason.LpcReceive; case 17: return ThreadWaitReason.LpcReply; case 18: return ThreadWaitReason.VirtualMemory; case 19: return ThreadWaitReason.PageOut; default: return ThreadWaitReason.Unknown; } } internal static string GetProcessShortName(string name) { if (string.IsNullOrEmpty(name)) return string.Empty; int num1 = -1; int startIndex1 = -1; for (int index = 0; index < name.Length; ++index) { if (name[index] == '\\') num1 = index; else if (name[index] == '.') startIndex1 = index; } int num2 = startIndex1 != -1 ? (!string.Equals(".exe", name.Substring(startIndex1), StringComparison.OrdinalIgnoreCase) ? name.Length - 1 : startIndex1 - 1) : name.Length - 1; int startIndex2 = num1 != -1 ? num1 + 1 : 0; return name.Substring(startIndex2, num2 - startIndex2 + 1); } [HostProtection(MayLeakOnAbort = true)] internal static class NativeMethods { public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); public const int STARTF_USESTDHANDLES = 0x00000100; public const int STD_INPUT_HANDLE = -10; public const int STD_OUTPUT_HANDLE = -11; public const int STD_ERROR_HANDLE = -12; public const int STILL_ACTIVE = 0x00000103; public const int SW_HIDE = 0; public const int WAIT_OBJECT_0 = 0x00000000; public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF); public const int WAIT_TIMEOUT = 0x00000102; public const int WAIT_ABANDONED = 0x00000080; public const int ERROR_BAD_EXE_FORMAT = 193; public const int ERROR_EXE_MACHINE_TYPE_MISMATCH = 216; [DllImport("ntdll.dll", CharSet = CharSet.Auto)] public static extern int NtQuerySystemInformation( int query, IntPtr dataPtr, int size, out int returnedSize); [StructLayout(LayoutKind.Sequential)] internal class SystemProcessInformation { internal uint NextEntryOffset; internal uint NumberOfThreads; private long SpareLi1; private long SpareLi2; private long SpareLi3; private long CreateTime; private long UserTime; private long KernelTime; internal ushort NameLength; internal ushort MaximumNameLength; internal IntPtr NamePtr; internal int BasePriority; internal IntPtr UniqueProcessId; internal IntPtr InheritedFromUniqueProcessId; internal uint HandleCount; internal uint SessionId; internal UIntPtr PageDirectoryBase; internal UIntPtr PeakVirtualSize; internal UIntPtr VirtualSize; internal uint PageFaultCount; internal UIntPtr PeakWorkingSetSize; internal UIntPtr WorkingSetSize; internal UIntPtr QuotaPeakPagedPoolUsage; internal UIntPtr QuotaPagedPoolUsage; internal UIntPtr QuotaPeakNonPagedPoolUsage; internal UIntPtr QuotaNonPagedPoolUsage; internal UIntPtr PagefileUsage; internal UIntPtr PeakPagefileUsage; internal UIntPtr PrivatePageCount; private long ReadOperationCount; private long WriteOperationCount; private long OtherOperationCount; private long ReadTransferCount; private long WriteTransferCount; private long OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] internal class SystemThreadInformation { private long KernelTime; private long UserTime; private long CreateTime; private uint WaitTime; internal IntPtr StartAddress; internal IntPtr UniqueProcess; internal IntPtr UniqueThread; internal int Priority; internal int BasePriority; internal uint ContextSwitches; internal uint ThreadState; internal uint WaitReason; } [StructLayout(LayoutKind.Sequential)] internal class STARTUPINFO { public int cb; public IntPtr lpReserved = IntPtr.Zero; public string lpDesktop = null; public IntPtr lpTitle = IntPtr.Zero; public int dwX = 0; public int dwY = 0; public int dwXSize = 0; public int dwYSize = 0; public int dwXCountChars = 0; public int dwYCountChars = 0; public int dwFillAttribute = 0; public int dwFlags; public short wShowWindow = 0; public short cbReserved2 = 0; public IntPtr lpReserved2 = IntPtr.Zero; public SafeFileHandle hStdInput = new SafeFileHandle(IntPtr.Zero, false); public SafeFileHandle hStdOutput = new SafeFileHandle(IntPtr.Zero, false); public SafeFileHandle hStdError = new SafeFileHandle(IntPtr.Zero, false); public STARTUPINFO() { cb = Marshal.SizeOf(this); } public void Dispose() { // close the handles created for child process if (hStdInput != null && !hStdInput.IsInvalid) { hStdInput.Close(); hStdInput = null; } if (hStdOutput != null && !hStdOutput.IsInvalid) { hStdOutput.Close(); hStdOutput = null; } if (hStdError != null && !hStdError.IsInvalid) { hStdError.Close(); hStdError = null; } } } [StructLayout(LayoutKind.Sequential)] internal class SECURITY_ATTRIBUTES { public int nLength = 12; public SafeLocalMemHandle lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false); public bool bInheritHandle; } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.None)] public static extern bool GetExitCodeProcess(SafeProcessHandle processHandle, out int exitCode); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.None)] public static extern bool GetProcessTimes(SafeProcessHandle handle, out long creation, out long exit, out long kernel, out long user); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] [ResourceExposure(ResourceScope.Process)] public static extern IntPtr GetStdHandle(int whichHandle); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.Process)] public static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SECURITY_ATTRIBUTES lpPipeAttributes, int nSize); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] [ResourceExposure(ResourceScope.Process)] public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, // LPCTSTR StringBuilder lpCommandLine, // LPTSTR - note: CreateProcess might insert a null somewhere in this string SECURITY_ATTRIBUTES lpProcessAttributes, // LPSECURITY_ATTRIBUTES SECURITY_ATTRIBUTES lpThreadAttributes, // LPSECURITY_ATTRIBUTES bool bInheritHandles, // BOOL int dwCreationFlags, // DWORD IntPtr lpEnvironment, // LPVOID [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, // LPCTSTR STARTUPINFO lpStartupInfo, // LPSTARTUPINFO PROCESS_INFORMATION lpProcessInformation // LPPROCESS_INFORMATION ); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.Machine)] public static extern bool TerminateProcess(SafeProcessHandle processHandle, int exitCode); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] [ResourceExposure(ResourceScope.Process)] public static extern IntPtr GetCurrentProcess(); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)] [SuppressUnmanagedCodeSecurityAttribute] [ResourceExposure(ResourceScope.Machine)] public static extern bool CreateProcessAsUser(Win32.TokensEx.SafeTokenHandle hToken, string lpApplicationName, StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Auto)] internal static extern bool CreateProcessWithToken( Win32.TokensEx.SafeTokenHandle hToken, LogonFlags dwLogonFlags, string lpApplicationName, StringBuilder lpCommandLine, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, BestFitMapping = false)] [ResourceExposure(ResourceScope.Machine)] internal static extern bool CreateProcessWithLogonW(string userName, string domain, IntPtr password, LogonFlags logonFlags, [MarshalAs(UnmanagedType.LPTStr)] string appName, StringBuilder cmdLine, int creationFlags, IntPtr environmentBlock, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, // LPCTSTR STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation); //TODO: TOKEN [StructLayout(LayoutKind.Sequential)] internal class PROCESS_INFORMATION { public IntPtr hProcess = IntPtr.Zero; public IntPtr hThread = IntPtr.Zero; public int dwProcessId = 0; public int dwThreadId = 0; } [Flags] internal enum LogonFlags { LOGON_WITH_PROFILE = 0x00000001, LOGON_NETCREDENTIALS_ONLY = 0x00000002 } public const int QS_KEY = 0x0001, QS_MOUSEMOVE = 0x0002, QS_MOUSEBUTTON = 0x0004, QS_POSTMESSAGE = 0x0008, QS_TIMER = 0x0010, QS_PAINT = 0x0020, QS_SENDMESSAGE = 0x0040, QS_HOTKEY = 0x0080, QS_ALLPOSTMESSAGE = 0x0100, QS_MOUSE = QS_MOUSEMOVE | QS_MOUSEBUTTON, QS_INPUT = QS_MOUSE | QS_KEY, QS_ALLEVENTS = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY, QS_ALLINPUT = QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.None)] public static extern int WaitForInputIdle(SafeProcessHandle handle, int milliseconds); [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.Machine)] public static extern bool ShellExecuteEx(ShellExecuteInfo info); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, BestFitMapping = false)] [ResourceExposure(ResourceScope.Machine)] public static extern bool DuplicateHandle(HandleRef hSourceProcessHandle, SafeHandle hSourceHandle, HandleRef hTargetProcess, out SafeFileHandle targetHandle, int dwDesiredAccess, bool bInheritHandle, int dwOptions); [DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true, BestFitMapping = false)] [ResourceExposure(ResourceScope.Machine)] public static extern bool DuplicateHandle(HandleRef hSourceProcessHandle, SafeHandle hSourceHandle, HandleRef hTargetProcess, out SafeWaitHandle targetHandle, int dwDesiredAccess, bool bInheritHandle, int dwOptions); [DllImport("user32.dll", CharSet = CharSet.Auto, BestFitMapping = true)] [ResourceExposure(ResourceScope.None)] public static extern int GetWindowText(HandleRef hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll", CharSet = CharSet.Auto)] [ResourceExposure(ResourceScope.None)] public static extern int GetWindowTextLength(HandleRef hWnd); [DllImport("user32.dll", CharSet = CharSet.Auto)] [ResourceExposure(ResourceScope.None)] public static extern IntPtr SendMessageTimeout(HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam, int flags, int timeout, out IntPtr pdwResult); [DllImport("user32.dll", CharSet = CharSet.Auto)] [ResourceExposure(ResourceScope.None)] public static extern int GetWindowLong(HandleRef hWnd, int nIndex); [DllImport("user32.dll", CharSet = CharSet.Auto)] [ResourceExposure(ResourceScope.None)] public static extern int PostMessage(HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam); [StructLayout(LayoutKind.Sequential)] internal class ShellExecuteInfo { public int cbSize; public int fMask; public IntPtr hwnd = (IntPtr)0; public IntPtr lpVerb = (IntPtr)0; public IntPtr lpFile = (IntPtr)0; public IntPtr lpParameters = (IntPtr)0; public IntPtr lpDirectory = (IntPtr)0; public int nShow; public IntPtr hInstApp = (IntPtr)0; public IntPtr lpIDList = (IntPtr)0; public IntPtr lpClass = (IntPtr)0; public IntPtr hkeyClass = (IntPtr)0; public int dwHotKey = 0; public IntPtr hIcon = (IntPtr)0; public IntPtr hProcess = (IntPtr)0; [ResourceExposure(ResourceScope.Machine)] public ShellExecuteInfo() { cbSize = Marshal.SizeOf(this); } } [StructLayout(LayoutKind.Sequential)] internal struct LUID { public int LowPart; public int HighPart; } public const int SEE_MASK_NOCLOSEPROCESS = 0x00000040; public const int SEE_MASK_CONNECTNETDRV = 0x00000080; public const int SEE_MASK_FLAG_DDEWAIT = 0x00000100; public const int SEE_MASK_DOENVSUBST = 0x00000200; public const int SEE_MASK_FLAG_NO_UI = 0x00000400; public const int PROCESS_TERMINATE = 0x0001; public const int PROCESS_QUERY_INFORMATION = 0x0400; public const int PROCESS_QUERY_LIMITED_INFORMATION = 0x1000; public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000; public const int SYNCHRONIZE = 0x00100000; public const int PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF; public const int READ_CONTROL = 0x00020000; public const int STANDARD_RIGHTS_READ = READ_CONTROL; public const int KEY_QUERY_VALUE = 0x0001; public const int KEY_ENUMERATE_SUB_KEYS = 0x0008; public const int KEY_NOTIFY = 0x0010; public const int ERROR_BROKEN_PIPE = 109; public const int ERROR_NO_DATA = 232; public const int ERROR_HANDLE_EOF = 38; public const int ERROR_IO_INCOMPLETE = 996; public const int ERROR_IO_PENDING = 997; public const int ERROR_FILE_EXISTS = 0x50; public const int ERROR_FILENAME_EXCED_RANGE = 0xCE; // filename too long. public const int ERROR_MORE_DATA = 234; public const int ERROR_CANCELLED = 1223; public const int ERROR_FILE_NOT_FOUND = 2; public const int ERROR_PATH_NOT_FOUND = 3; public const int ERROR_ACCESS_DENIED = 5; public const int ERROR_INVALID_HANDLE = 6; public const int ERROR_NOT_ENOUGH_MEMORY = 8; public const int ERROR_BAD_COMMAND = 22; public const int ERROR_SHARING_VIOLATION = 32; public const int ERROR_OPERATION_ABORTED = 995; public const int ERROR_NO_ASSOCIATION = 1155; public const int ERROR_DLL_NOT_FOUND = 1157; public const int ERROR_DDE_FAIL = 1156; public const int ERROR_INVALID_PARAMETER = 87; public const int ERROR_PARTIAL_COPY = 299; public const int ERROR_SUCCESS = 0; public const int ERROR_ALREADY_EXISTS = 183; public const int ERROR_COUNTER_TIMEOUT = 1121; public const int DUPLICATE_CLOSE_SOURCE = 1; public const int DUPLICATE_SAME_ACCESS = 2; public const int SE_ERR_FNF = 2; public const int SE_ERR_PNF = 3; public const int SE_ERR_ACCESSDENIED = 5; public const int SE_ERR_OOM = 8; public const int SE_ERR_DLLNOTFOUND = 32; public const int SE_ERR_SHARE = 26; public const int SE_ERR_ASSOCINCOMPLETE = 27; public const int SE_ERR_DDETIMEOUT = 28; public const int SE_ERR_DDEFAIL = 29; public const int SE_ERR_DDEBUSY = 30; public const int SE_ERR_NOASSOC = 31; public const int CREATE_NO_WINDOW = 0x08000000; public const int CREATE_SUSPENDED = 0x00000004; public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; public const int SMTO_ABORTIFHUNG = 0x0002; public const int GWL_STYLE = -16; public const int GCL_WNDPROC = -24; public const int GWL_WNDPROC = -4; public const int WS_DISABLED = 0x08000000; public const int WM_NULL = 0x0000; public const int WM_CLOSE = 0x0010; public const int SW_SHOWNORMAL = 1; public const int SW_NORMAL = 1; public const int SW_SHOWMINIMIZED = 2; public const int SW_SHOWMAXIMIZED = 3; public const int SW_MAXIMIZE = 3; public const int SW_SHOWNOACTIVATE = 4; public const int SW_SHOW = 5; public const int SW_MINIMIZE = 6; public const int SW_SHOWMINNOACTIVE = 7; public const int SW_SHOWNA = 8; public const int SW_RESTORE = 9; public const int SW_SHOWDEFAULT = 10; public const int SW_MAX = 10; public const int GW_OWNER = 4; public const int WHITENESS = 0x00FF0062; } internal delegate void UserCallBack(String data); internal class AsyncStreamReader : IDisposable { internal const int DefaultBufferSize = 1024; // Byte buffer size private const int MinBufferSize = 128; private Stream stream; private Encoding encoding; private Decoder decoder; private byte[] byteBuffer; private char[] charBuffer; // Record the number of valid bytes in the byteBuffer, for a few checks. // This is the maximum number of chars we can get from one call to // ReadBuffer. Used so ReadBuffer can tell when to copy data into // a user's char[] directly, instead of our internal char[]. private int _maxCharsPerBuffer; // Store a backpointer to the process class, to check for user callbacks private Process process; // Delegate to call user function. private UserCallBack userCallBack; // Internal Cancel operation private bool cancelOperation; private ManualResetEvent eofEvent; private Queue messageQueue; private StringBuilder sb; private bool bLastCarriageReturn; // Cache the last position scanned in sb when searching for lines. private int currentLinePos; internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding) : this(process, stream, callback, encoding, DefaultBufferSize) { } // Creates a new AsyncStreamReader for the given stream. The // character encoding is set by encoding and the buffer size, // in number of 16-bit characters, is set by bufferSize. // internal AsyncStreamReader(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) { Init(process, stream, callback, encoding, bufferSize); messageQueue = new Queue(); } private void Init(Process process, Stream stream, UserCallBack callback, Encoding encoding, int bufferSize) { this.process = process; this.stream = stream; this.encoding = encoding; this.userCallBack = callback; decoder = encoding.GetDecoder(); if (bufferSize < MinBufferSize) bufferSize = MinBufferSize; byteBuffer = new byte[bufferSize]; _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize); charBuffer = new char[_maxCharsPerBuffer]; cancelOperation = false; eofEvent = new ManualResetEvent(false); sb = null; this.bLastCarriageReturn = false; } public virtual void Close() { Dispose(true); } void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (stream != null) stream.Close(); } if (stream != null) { stream = null; encoding = null; decoder = null; byteBuffer = null; charBuffer = null; } if (eofEvent != null) { eofEvent.Close(); eofEvent = null; } } public virtual Encoding CurrentEncoding { get { return encoding; } } public virtual Stream BaseStream { get { return stream; } } // User calls BeginRead to start the asynchronous read internal void BeginReadLine() { if (cancelOperation) { cancelOperation = false; } if (sb == null) { sb = new StringBuilder(DefaultBufferSize); stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); } else { FlushMessageQueue(); } } internal void CancelOperation() { cancelOperation = true; } // This is the async callback function. Only one thread could/should call this. private void ReadBuffer(IAsyncResult ar) { int byteLen; try { byteLen = stream.EndRead(ar); } catch (IOException) { // We should ideally consume errors from operations getting cancelled // so that we don't crash the unsuspecting parent with an unhandled exc. // This seems to come in 2 forms of exceptions (depending on platform and scenario), // namely OperationCanceledException and IOException (for errorcode that we don't // map explicitly). byteLen = 0; // Treat this as EOF } catch (OperationCanceledException) { // We should consume any OperationCanceledException from child read here // so that we don't crash the parent with an unhandled exc byteLen = 0; // Treat this as EOF } if (byteLen == 0) { // We're at EOF, we won't call this function again from here on. lock (messageQueue) { if (sb.Length != 0) { messageQueue.Enqueue(sb.ToString()); sb.Length = 0; } messageQueue.Enqueue(null); } try { // UserCallback could throw, we should still set the eofEvent FlushMessageQueue(); } finally { eofEvent.Set(); } } else { int charLen = decoder.GetChars(byteBuffer, 0, byteLen, charBuffer, 0); sb.Append(charBuffer, 0, charLen); GetLinesFromStringBuilder(); stream.BeginRead(byteBuffer, 0, byteBuffer.Length, new AsyncCallback(ReadBuffer), null); } } // Read lines stored in StringBuilder and the buffer we just read into. // A line is defined as a sequence of characters followed by // a carriage return ('\r'), a line feed ('\n'), or a carriage return // immediately followed by a line feed. The resulting string does not // contain the terminating carriage return and/or line feed. The returned // value is null if the end of the input stream has been reached. // private void GetLinesFromStringBuilder() { int currentIndex = currentLinePos; int lineStart = 0; int len = sb.Length; // skip a beginning '\n' character of new block if last block ended // with '\r' if (bLastCarriageReturn && (len > 0) && sb[0] == '\n') { currentIndex = 1; lineStart = 1; bLastCarriageReturn = false; } while (currentIndex < len) { char ch = sb[currentIndex]; // Note the following common line feed chars: // \n - UNIX \r\n - DOS \r - Mac if (ch == '\r' || ch == '\n') { string s = sb.ToString(lineStart, currentIndex - lineStart); lineStart = currentIndex + 1; // skip the "\n" character following "\r" character if ((ch == '\r') && (lineStart < len) && (sb[lineStart] == '\n')) { lineStart++; currentIndex++; } lock (messageQueue) { messageQueue.Enqueue(s); } } currentIndex++; } // Protect length as IndexOutOfRangeException was being thrown when less than a // character's worth of bytes was read at the beginning of a line. if (len > 0 && sb[len - 1] == '\r') { bLastCarriageReturn = true; } // Keep the rest characaters which can't form a new line in string builder. if (lineStart < len) { if (lineStart == 0) { // we found no breaklines, in this case we cache the position // so next time we don't have to restart from the beginning currentLinePos = currentIndex; } else { sb.Remove(0, lineStart); currentLinePos = 0; } } else { sb.Length = 0; currentLinePos = 0; } FlushMessageQueue(); } private void FlushMessageQueue() { while (true) { // When we call BeginReadLine, we also need to flush the queue // So there could be a ---- between the ReadBuffer and BeginReadLine // We need to take lock before DeQueue. if (messageQueue.Count > 0) { lock (messageQueue) { if (messageQueue.Count > 0) { string s = (string)messageQueue.Dequeue(); // skip if the read is the read is cancelled // this might happen inside UserCallBack // However, continue to drain the queue if (!cancelOperation) { userCallBack(s); } } } } else { break; } } } // Wait until we hit EOF. This is called from Process.WaitForExit // We will lose some information if we don't do this. internal void WaitUtilEOF() { if (eofEvent != null) { eofEvent.WaitOne(); eofEvent.Close(); eofEvent = null; } } } public delegate void DataReceivedEventHandler(Object sender, DataReceivedEventArgs e); public class DataReceivedEventArgs : EventArgs { internal string _data; internal DataReceivedEventArgs(string data) => this._data = data; public string Data => this._data; } internal class ProcessWaitHandle : WaitHandle { [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] internal ProcessWaitHandle(SafeProcessHandle processHandle) : base() { SafeWaitHandle waitHandle = null; bool succeeded = NativeMethods.DuplicateHandle(new HandleRef(this, NativeMethods.GetCurrentProcess()), processHandle, new HandleRef(this, NativeMethods.GetCurrentProcess()), out waitHandle, 0, false, NativeMethods.DUPLICATE_SAME_ACCESS); if (!succeeded) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } this.SafeWaitHandle = waitHandle; } } [SuppressUnmanagedCodeSecurityAttribute] internal sealed class SafeThreadHandle : SafeHandleZeroOrMinusOneIsInvalid { internal SafeThreadHandle() : base(true) { } internal void InitialSetHandle(IntPtr h) { Debug.Assert(base.IsInvalid, "Safe handle should only be set once"); base.SetHandle(h); } override protected bool ReleaseHandle() { return CloseHandle(handle); } [DllImport("kernel32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] public static extern bool CloseHandle(IntPtr handle); } [HostProtectionAttribute(MayLeakOnAbort = true)] [SuppressUnmanagedCodeSecurityAttribute] internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid { [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) { SetHandle(existingHandle); } [DllImport("kernel32.dll")] [ResourceExposure(ResourceScope.None)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] private static extern IntPtr LocalFree(IntPtr hMem); override protected bool ReleaseHandle() { return LocalFree(handle) == IntPtr.Zero; } } [SuppressUnmanagedCodeSecurityAttribute] public sealed class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid { internal static SafeProcessHandle InvalidHandle = new SafeProcessHandle(IntPtr.Zero); // Note that OpenProcess returns 0 on failure internal SafeProcessHandle() : base(true) { } internal SafeProcessHandle(IntPtr handle) : base(true) { SetHandle(handle); } [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public SafeProcessHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) { SetHandle(existingHandle); } [DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] [ResourceExposure(ResourceScope.Machine)] internal static extern SafeProcessHandle OpenProcess(int access, bool inherit, int processId); internal void InitialSetHandle(IntPtr h) { Debug.Assert(base.IsInvalid, "Safe handle should only be set once"); base.handle = h; } override protected bool ReleaseHandle() { return CloseHandle(handle); } [DllImport("kernel32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] public static extern bool CloseHandle(IntPtr handle); } [TypeConverter(typeof(ExpandableObjectConverter)), // Disabling partial trust scenarios PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust"), HostProtection(SharedState = true, SelfAffectingProcessMgmt = true)] public sealed class ProcessStartInfo { string fileName; string arguments; string directory; string verb; ProcessWindowStyle windowStyle; bool errorDialog; IntPtr errorDialogParentHandle; bool useShellExecute = false; string userName; string domain; SecureString password; string passwordInClearText; bool loadUserProfile; bool redirectStandardInput = false; bool redirectStandardOutput = false; bool redirectStandardError = false; Encoding standardOutputEncoding; Encoding standardErrorEncoding; bool createNoWindow = false; bool useSession0 = false; WeakReference weakParentProcess; internal StringDictionary environmentVariables; /// /// Default constructor. At least the /// property must be set before starting the process. /// public ProcessStartInfo() { } internal ProcessStartInfo(Process parent) { this.weakParentProcess = new WeakReference(parent); } /// /// Specifies the name of the application or document that is to be started. /// [ResourceExposure(ResourceScope.Machine)] public ProcessStartInfo(string fileName) { this.fileName = fileName; } /// /// Specifies the name of the application that is to be started, as well as a set /// of command line arguments to pass to the application. /// [ResourceExposure(ResourceScope.Machine)] public ProcessStartInfo(string fileName, string arguments) { this.fileName = fileName; this.arguments = arguments; } /// /// /// Specifies the verb to use when opening the filename. For example, the "print" /// verb will print a document specified using . /// Each file extension has it's own set of verbs, which can be obtained using the /// property. /// The default verb can be specified using "". /// /// /// Discuss 'opening' vs. 'starting.' I think the part about the /// default verb was a dev comment. /// Find out what /// that means. /// /// public string Verb { get { if (verb == null) return string.Empty; return verb; } set { verb = value; } } public string Arguments { get { if (arguments == null) return string.Empty; return arguments; } set { arguments = value; } } public bool CreateNoWindow { get { return createNoWindow; } set { createNoWindow = value; } } public bool UseSession0 { get { return useSession0; } set { useSession0 = value; } } public StringDictionary EnvironmentVariables { [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] get { // Note: // Creating a detached ProcessStartInfo will pre-populate the environment // with current environmental variables. // When used with an existing Process.ProcessStartInfo the following behavior // * Desktop - Populates with current Environment (rather than that of the process) if (environmentVariables == null) { environmentVariables = new StringDictionaryWithComparer(); // if not in design mode, initialize the child environment block with all the parent variables if (!(this.weakParentProcess != null && this.weakParentProcess.IsAlive && ((Component)this.weakParentProcess.Target).Site != null && ((Component)this.weakParentProcess.Target).Site.DesignMode)) { foreach (DictionaryEntry entry in System.Environment.GetEnvironmentVariables()) environmentVariables.Add((string)entry.Key, (string)entry.Value); } } return environmentVariables; } } private IDictionary environment; public IDictionary Environment { get { if (environment == null) { environment = this.EnvironmentVariables.AsGenericDictionary(); } return environment; } } public bool RedirectStandardInput { get { return redirectStandardInput; } set { redirectStandardInput = value; } } public bool RedirectStandardOutput { get { return redirectStandardOutput; } set { redirectStandardOutput = value; } } public bool RedirectStandardError { get { return redirectStandardError; } set { redirectStandardError = value; } } public Encoding StandardErrorEncoding { get { return standardErrorEncoding; } set { standardErrorEncoding = value; } } public Encoding StandardOutputEncoding { get { return standardOutputEncoding; } set { standardOutputEncoding = value; } } public bool UseShellExecute { get { return useShellExecute; } set { useShellExecute = value; } } /// /// Returns the set of verbs associated with the file specified by the /// property. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public string[] Verbs { [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] get { ArrayList verbs = new ArrayList(); RegistryKey key = null; string extension = Path.GetExtension(FileName); try { if (extension != null && extension.Length > 0) { key = Registry.ClassesRoot.OpenSubKey(extension); if (key != null) { string value = (string)key.GetValue(String.Empty); key.Close(); key = Registry.ClassesRoot.OpenSubKey(value + "\\shell"); if (key != null) { string[] names = key.GetSubKeyNames(); for (int i = 0; i < names.Length; i++) if (string.Compare(names[i], "new", StringComparison.OrdinalIgnoreCase) != 0) verbs.Add(names[i]); key.Close(); key = null; } } } } finally { if (key != null) key.Close(); } string[] temp = new string[verbs.Count]; verbs.CopyTo(temp, 0); return temp; } } public string UserName { get { if (userName == null) { return string.Empty; } else { return userName; } } set { userName = value; } } public SecureString Password { get { return password; } set { password = value; } } public string PasswordInClearText { get { return passwordInClearText; } set { passwordInClearText = value; } } public string Domain { get { if (domain == null) { return string.Empty; } else { return domain; } } set { domain = value; } } public bool LoadUserProfile { get { return loadUserProfile; } set { loadUserProfile = value; } } public string FileName { [ResourceExposure(ResourceScope.Machine)] get { if (fileName == null) return string.Empty; return fileName; } [ResourceExposure(ResourceScope.Machine)] set { fileName = value; } } public string WorkingDirectory { [ResourceExposure(ResourceScope.Machine)] get { if (directory == null) return string.Empty; return directory; } [ResourceExposure(ResourceScope.Machine)] set { directory = value; } } public bool ErrorDialog { get { return errorDialog; } set { errorDialog = value; } } [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IntPtr ErrorDialogParentHandle { get { return errorDialogParentHandle; } set { errorDialogParentHandle = value; } } public ProcessWindowStyle WindowStyle { get { return windowStyle; } set { if (!Enum.IsDefined(typeof(ProcessWindowStyle), value)) throw new InvalidEnumArgumentException("value", (int)value, typeof(ProcessWindowStyle)); windowStyle = value; } } } [Serializable] internal class StringDictionaryWithComparer : StringDictionary { public StringDictionaryWithComparer() : this((IEqualityComparer)StringComparer.OrdinalIgnoreCase) { } public StringDictionaryWithComparer(IEqualityComparer comparer) => this.ReplaceHashtable(new Hashtable(comparer)); public override string this[string key] { get => key != null ? (string)this.contents[(object)key] : throw new ArgumentNullException(nameof(key)); set { if (key == null) throw new ArgumentNullException(nameof(key)); this.contents[(object)key] = (object)value; } } public override void Add(string key, string value) { if (key == null) throw new ArgumentNullException(nameof(key)); this.contents.Add((object)key, (object)value); } public override bool ContainsKey(string key) => key != null ? this.contents.ContainsKey((object)key) : throw new ArgumentNullException(nameof(key)); public override void Remove(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); this.contents.Remove((object)key); } } [Serializable] public class StringDictionary : IEnumerable { internal Hashtable contents = new Hashtable(); /// Gets the number of key/value pairs in the . /// The number of key/value pairs in the . /// Retrieving the value of this property is an O(1) operation. public virtual int Count => this.contents.Count; /// Gets a value indicating whether access to the is synchronized (thread safe). /// /// if access to the is synchronized (thread safe); otherwise, . public virtual bool IsSynchronized => this.contents.IsSynchronized; /// Gets or sets the value associated with the specified key. /// The key whose value to get or set. /// The value associated with the specified key. If the specified key is not found, Get returns , and Set creates a new entry with the specified key. /// /// is . public virtual string this[string key] { get { if (key == null) throw new ArgumentNullException(nameof(key)); return (string)this.contents[(object)key.ToLower(CultureInfo.InvariantCulture)]; } set { if (key == null) throw new ArgumentNullException(nameof(key)); this.contents[(object)key.ToLower(CultureInfo.InvariantCulture)] = (object)value; } } /// Gets a collection of keys in the . /// An that provides the keys in the . public virtual ICollection Keys => this.contents.Keys; /// Gets an object that can be used to synchronize access to the . /// An that can be used to synchronize access to the . public virtual object SyncRoot => this.contents.SyncRoot; /// Gets a collection of values in the . /// An that provides the values in the . public virtual ICollection Values => this.contents.Values; /// Adds an entry with the specified key and value into the . /// The key of the entry to add. /// The value of the entry to add. The value can be . /// /// is . /// An entry with the same key already exists in the . /// The is read-only. public virtual void Add(string key, string value) { if (key == null) throw new ArgumentNullException(nameof(key)); this.contents.Add((object)key.ToLower(CultureInfo.InvariantCulture), (object)value); } /// Removes all entries from the . /// The is read-only. public virtual void Clear() => this.contents.Clear(); /// Determines if the contains a specific key. /// The key to locate in the . /// /// if the contains an entry with the specified key; otherwise, . /// The key is . public virtual bool ContainsKey(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return this.contents.ContainsKey((object)key.ToLower(CultureInfo.InvariantCulture)); } /// Determines if the contains a specific value. /// The value to locate in the . The value can be . /// /// if the contains an element with the specified value; otherwise, . public virtual bool ContainsValue(string value) => this.contents.ContainsValue((object)value); /// Copies the string dictionary values to a one-dimensional instance at the specified index. /// The one-dimensional that is the destination of the values copied from the . /// The index in the array where copying begins. /// /// is multidimensional. /// -or- /// The number of elements in the is greater than the available space from to the end of . /// /// is . /// /// is less than the lower bound of . public virtual void CopyTo(Array array, int index) => this.contents.CopyTo(array, index); /// Returns an enumerator that iterates through the string dictionary. /// An that iterates through the string dictionary. public virtual IEnumerator GetEnumerator() => (IEnumerator)this.contents.GetEnumerator(); /// Removes the entry with the specified key from the string dictionary. /// The key of the entry to remove. /// The key is . /// The is read-only. public virtual void Remove(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); this.contents.Remove((object)key.ToLower(CultureInfo.InvariantCulture)); } internal void ReplaceHashtable(Hashtable useThisHashtableInstead) => this.contents = useThisHashtableInstead; internal IDictionary AsGenericDictionary() => (IDictionary)new StringDictionary.GenericAdapter(this); private class GenericAdapter : IDictionary, ICollection>, IEnumerable>, IEnumerable { private StringDictionary m_stringDictionary; private StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter _values; private StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter _keys; internal GenericAdapter(StringDictionary stringDictionary) => this.m_stringDictionary = stringDictionary; public void Add(string key, string value) => this[key] = value; public bool ContainsKey(string key) => this.m_stringDictionary.ContainsKey(key); public void Clear() => this.m_stringDictionary.Clear(); public int Count => this.m_stringDictionary.Count; public string this[string key] { get { if (key == null) throw new ArgumentNullException(nameof(key)); return this.m_stringDictionary.ContainsKey(key) ? this.m_stringDictionary[key] : throw new KeyNotFoundException(); } set { if (key == null) throw new ArgumentNullException(nameof(key)); this.m_stringDictionary[key] = value; } } public ICollection Keys { get { if (this._keys == null) this._keys = new StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter(this.m_stringDictionary, StringDictionary.GenericAdapter.KeyOrValue.Key); return (ICollection)this._keys; } } public ICollection Values { get { if (this._values == null) this._values = new StringDictionary.GenericAdapter.ICollectionToGenericCollectionAdapter(this.m_stringDictionary, StringDictionary.GenericAdapter.KeyOrValue.Value); return (ICollection)this._values; } } public bool Remove(string key) { if (!this.m_stringDictionary.ContainsKey(key)) return false; this.m_stringDictionary.Remove(key); return true; } public bool TryGetValue(string key, out string value) { if (!this.m_stringDictionary.ContainsKey(key)) { value = (string)null; return false; } value = this.m_stringDictionary[key]; return true; } void ICollection>.Add(KeyValuePair item) => this.m_stringDictionary.Add(item.Key, item.Value); bool ICollection>.Contains(KeyValuePair item) { string str; return this.TryGetValue(item.Key, out str) && str.Equals(item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array), "ArgumentNull_Array"); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "ArgumentOutOfRange_NeedNonNegNum"); if (array.Length - arrayIndex < this.Count) throw new ArgumentException("Arg_ArrayPlusOffTooSmall"); int num = arrayIndex; foreach (DictionaryEntry dictionaryEntry in this.m_stringDictionary) array[num++] = new KeyValuePair((string)dictionaryEntry.Key, (string)dictionaryEntry.Value); } bool ICollection>.IsReadOnly => false; bool ICollection>.Remove(KeyValuePair item) { if (!((ICollection>)this).Contains(item)) return false; this.m_stringDictionary.Remove(item.Key); return true; } IEnumerator IEnumerable.GetEnumerator() => (IEnumerator)this.GetEnumerator(); public IEnumerator> GetEnumerator() { foreach (DictionaryEntry dictionaryEntry in this.m_stringDictionary) yield return new KeyValuePair((string)dictionaryEntry.Key, (string)dictionaryEntry.Value); } internal enum KeyOrValue { Key, Value, } private class ICollectionToGenericCollectionAdapter : ICollection, IEnumerable, IEnumerable { private StringDictionary _internal; private StringDictionary.GenericAdapter.KeyOrValue _keyOrValue; public ICollectionToGenericCollectionAdapter(StringDictionary source, StringDictionary.GenericAdapter.KeyOrValue keyOrValue) { this._internal = source != null ? source : throw new ArgumentNullException(nameof(source)); this._keyOrValue = keyOrValue; } public void Add(string item) => this.ThrowNotSupportedException(); public void Clear() => this.ThrowNotSupportedException(); public void ThrowNotSupportedException() { if (this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key) throw new NotSupportedException("NotSupported_KeyCollectionSet"); throw new NotSupportedException("NotSupported_ValueCollectionSet"); } public bool Contains(string item) => this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key ? this._internal.ContainsKey(item) : this._internal.ContainsValue(item); public void CopyTo(string[] array, int arrayIndex) => this.GetUnderlyingCollection().CopyTo((Array)array, arrayIndex); public int Count => this._internal.Count; public bool IsReadOnly => true; public bool Remove(string item) { this.ThrowNotSupportedException(); return false; } private ICollection GetUnderlyingCollection() => this._keyOrValue == StringDictionary.GenericAdapter.KeyOrValue.Key ? this._internal.Keys : this._internal.Values; public IEnumerator GetEnumerator() { foreach (string underlying in (IEnumerable)this.GetUnderlyingCollection()) yield return underlying; } IEnumerator IEnumerable.GetEnumerator() => this.GetUnderlyingCollection().GetEnumerator(); } } } } } ================================================ FILE: TrustedUninstaller.Shared/ControlWriter.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TrustedUninstaller.Shared { class Class1 { } } ================================================ FILE: TrustedUninstaller.Shared/Defender.cs ================================================ using System.IO; using System.Windows; using Core.Actions; using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration.Install; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Principal; using System.ServiceProcess; using System.Text; using System.Text.RegularExpressions; using System.Threading; using Core; using Interprocess; using JetBrains.Annotations; using Microsoft.Win32; using Microsoft.Win32.SafeHandles; using Microsoft.Win32.TaskScheduler; namespace TrustedUninstaller.Shared { public static class Defender { private static KeyValuePair[] DefenderItems = { new KeyValuePair("CompatTelRunner", ProcessType.Exe), new KeyValuePair("DWWIN", ProcessType.Exe), new KeyValuePair("DeviceCensus", ProcessType.Exe), new KeyValuePair("GameBarPresenceWriter", ProcessType.Exe), new KeyValuePair("SecurityHealthHost", ProcessType.Exe), new KeyValuePair("SecurityHealthService", ProcessType.Exe), // SecurityHealthService new KeyValuePair("SecurityHealthSystray", ProcessType.Exe), new KeyValuePair("smartscreen", ProcessType.Exe), //new KeyValuePair("MpCmdRun", ProcessType.Exe), new KeyValuePair("NisSrv", ProcessType.Exe), new KeyValuePair("wscsvc", ProcessType.Service), // Windows Security Center new KeyValuePair("WinDefend", ProcessType.Service), // Microsoft Defender Antivirus Service new KeyValuePair("Sense", ProcessType.Service), // Windows Defender Advanced Threat Protection Service new KeyValuePair("WdNisSvc", ProcessType.Service), // Microsoft Defender Antivirus Network Inspection Service new KeyValuePair("WdNisDrv", ProcessType.Device), // Microsoft Defender Antivirus Network Inspection Driver //new KeyValuePair("WdFilter", ProcessType.Device), // Windows Defender Disk inspection Minifilter, }; //[DllImport("Unlocker.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] //private static extern bool EzUnlockFileW(string path); private static readonly string[] defenderDirs = { Environment.ExpandEnvironmentVariables(@"%ProgramData%\Microsoft\Windows Defender"), Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\Windows Defender"), Environment.ExpandEnvironmentVariables(@"%ProgramFiles%\Windows Defender Advanced Threat Protection") }; private static void RenameAllChildFiles(string dir, bool reset) { foreach (var subDir in Directory.GetDirectories(dir)) { try { RenameAllChildFiles(subDir, reset); } catch (Exception e) { } } foreach (var file in Directory.GetFiles(dir, reset ? "*.oldx" : "*", SearchOption.TopDirectoryOnly)) { try { File.Move(file, reset ? file.Substring(0, file.Length - 4) : file + ".oldx"); Log.EnqueueSafe(LogType.Info, "PASS: " + file, new SerializableTrace()); } catch (Exception e) { Log.EnqueueExceptionSafe(e, file); } } } [InterprocessMethod(Level.Administrator)] private static bool DisableDefenderPrivileged() { try { Defender.Disable(); } catch (Exception ex) { Log.WriteExceptionSafe(ex, $"First Defender disable failed from second process."); Defender.Kill(); try { Defender.Disable(); } catch (Exception e) { Log.WriteExceptionSafe(e, $"Could not disable Windows Defender from second process."); throw; } } return true; } [InterprocessMethod(Level.Administrator)] public static void Cripple() { foreach (var defenderDir in defenderDirs) { try { RenameAllChildFiles(defenderDir, false); } catch (Exception e) { Log.WriteExceptionSafe(e, "Error renaming files in Defender directory.", ("Directory", defenderDir)); } } } [InterprocessMethod(Level.Administrator)] public static void DeCripple() { if (!DisableDefenderPrivileged()) Log.EnqueueSafe(LogType.Error, "Defender disable after restart failed.", new SerializableTrace()); foreach (var defenderDir in defenderDirs) { try { RenameAllChildFiles(defenderDir, true); } catch (Exception e) { } } } [InterprocessMethod(Level.Administrator)] public static void DisableBlocklist(InterLink.InterProgress progress, InterLink.InterMessageReporter reporter, bool UCPD) { if (UCPD) DisableUCPD(null); progress.Report(10); Thread.Sleep(250); try { // Can cause ProcessHacker driver warnings without this new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown error"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First memory integrity disable failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), Exe = $"NSudoLC.exe", Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity"" /v Enabled /d 0 /f", CreateWindow = false }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not disable memory integrity.", new SerializableTrace()); } progress.Report(60); Thread.Sleep(250); try { // Can cause ProcessHacker driver warnings without this new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown error"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First blocklist disable failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), Exe = $"NSudoLC.exe", Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\CI\Config"" /v VulnerableDriverBlocklistEnable /d 0 /f", CreateWindow = false }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not disable blocklist.", new SerializableTrace()); } progress.Report(100); } [InterprocessMethod(Level.Administrator)] public static void DisableUCPD([CanBeNull] InterLink.InterProgress progress) { try { using (TaskService ts = new TaskService()) { var task = ts.GetTask(@"\Microsoft\Windows\AppxDeploymentClient\UCPD velocity"); if (task != null) { task.Definition.Settings.Enabled = false; task.Enabled = false; task.RegisterChanges(); } } } catch (Exception e) { Log.EnqueueExceptionSafe(e, "Failed to disable UCPD task."); } if (progress != null) { progress.Report(10); Thread.Sleep(250); } try { new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\UCPD", Value = "Start", Data = 4, }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\UCPD", Value = "Start", Data = 4, }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown error"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First memory integrity disable failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), Exe = $"NSudoLC.exe", Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Services\UCPD"" /v Start /d 4 /f", CreateWindow = false }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\UCPD", Value = "Start", Data = 4, }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not disable memory integrity.", new SerializableTrace()); } if (progress != null) { progress.Report(80); Thread.Sleep(250); progress.Report(100); } } [InterprocessMethod(Level.Administrator)] public static bool KillAndDisable(InterLink.InterProgress progress, InterLink.InterMessageReporter reporter, bool forceSafeBoot, bool noSafeBoot) { try { if (!forceSafeBoot) { Thread.Sleep(250); reporter.Report("Disabling UCPD..."); DisableUCPD(null); Thread.Sleep(350); progress.Report(2); reporter.Report("Extracting service package..."); string cabPath = null; cabPath = ExtractCab(); progress.Report(4); reporter.Report("Adding certificate..."); var certPath = Path.GetTempFileName(); int exitCode; exitCode = RunPSCommand( $"try {{" + $"$cert = (Get-AuthenticodeSignature '{cabPath}').SignerCertificate; " + $"[System.IO.File]::WriteAllBytes('{certPath}', $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)); " + $"Import-Certificate '{certPath}' -CertStoreLocation 'Cert:\\LocalMachine\\Root' | Out-Null; " + $"Copy-Item -Path \"HKLM:\\Software\\Microsoft\\SystemCertificates\\ROOT\\Certificates\\$($cert.Thumbprint)\" \"HKLM:\\Software\\Microsoft\\SystemCertificates\\ROOT\\Certificates\\8A334AA8052DD244A647306A76B8178FA215F344\" -Force | Out-Null; " + $"EXIT 0; " + $"}} catch {{EXIT 1}}", null, null); Thread.Sleep(250); if (exitCode == 1) throw new Exception("Could not add certificate."); progress.Report(10); reporter.Report("Applying service package..."); string err = null; decimal lastProgress = 10; double lastDismProgress = 0; exitCode = RunCommand("DISM.exe", $"/Online /Add-Package /PackagePath:\"{cabPath}\" /NoRestart /IgnoreCheck", (sender, args) => { if (args.Data != null && args.Data.Contains("%")) { int i = args.Data.IndexOf('%') - 1; while (args.Data[i] == '.' || Char.IsDigit(args.Data[i])) i--; if (double.TryParse(args.Data.Substring(i + 1, args.Data.IndexOf('%') - i - 1), out double dismProgress)) { if (lastDismProgress == dismProgress) return; lastProgress = (decimal)((dismProgress / 100) * 80) + 10; progress.Report(lastProgress); lastDismProgress = dismProgress; } } }, ((sender, args) => { if (err == null && args.Data != null) err = args.Data; else if (err != null && args.Data != null) err = err + Environment.NewLine + args.Data; })); if (exitCode != 0 && exitCode != 3010) { if (noSafeBoot) { Console.WriteLine("\r\nDefender removal package application failed. Please restart and try again."); Environment.Exit(1); return false; } Log.EnqueueSafe(LogType.Info, "Live dism application failed: " + err, new SerializableTrace(), ("Exit code", exitCode)); reporter.Report("Removing certificate..."); exitCode = RunPSCommand( $"$cert = (Get-AuthenticodeSignature '{cabPath}').SignerCertificate; " + $"Get-ChildItem 'Cert:\\LocalMachine\\Root\\$($cert.Thumbprint)' | Remove-Item -Force | Out-Null; " + $"Remove-Item \"HKLM:\\Software\\Microsoft\\SystemCertificates\\ROOT\\Certificates\\8A334AA8052DD244A647306A76B8178FA215F344\" -Force -Recurse | Out-Null" , null, null); progress.Report(lastProgress + 5); reporter.Report("Adding service..."); InstallService(); Thread.Sleep(2000); progress.Report(95); reporter.Report("Enabling SafeBoot..."); RunCommand("bcdedit.exe", "/set {current} safeboot minimal", null, null); Thread.Sleep(500); progress.Report(100); return false; } progress.Report(90); reporter.Report("Removing certificate..."); exitCode = RunPSCommand( $"$cert = (Get-AuthenticodeSignature '{cabPath}').SignerCertificate; " + $"Get-ChildItem 'Cert:\\LocalMachine\\Root\\$($cert.Thumbprint)' | Remove-Item -Force | Out-Null; " + $"Remove-Item \"HKLM:\\Software\\Microsoft\\SystemCertificates\\ROOT\\Certificates\\8A334AA8052DD244A647306A76B8178FA215F344\" -Force -Recurse | Out-Null" , null, null); try { File.Delete(cabPath); } catch { } progress.Report(100); return true; } else { Thread.Sleep(250); progress.Report(2); reporter.Report("Disabling UCPD..."); DisableUCPD(null); Thread.Sleep(350); progress.Report(4); reporter.Report("Adding service..."); Thread.Sleep(500); progress.Report(10); InstallService(); Thread.Sleep(1000); progress.Report(25); Thread.Sleep(1200); progress.Report(50); Thread.Sleep(1300); progress.Report(70); reporter.Report("Enabling SafeBoot..."); RunCommand("bcdedit.exe", "/set {current} safeboot minimal", null, null); Thread.Sleep(2000); progress.Report(100); return false; } } catch (Exception e) { Log.EnqueueExceptionSafe(e); throw; } } private static void InstallService() { bool displayLastUsername = EnableDontDisplayLastUsername(); IntPtr scm = Win32.Service.OpenSCManager(null, null, Win32.Service.SCM_ACCESS.SC_MANAGER_CREATE_SERVICE); if (scm == IntPtr.Zero) throw new ApplicationException("Could not connect to service control manager."); try { IntPtr service = Win32.Service.OpenService(scm, "AMEPrepare", Win32.Service.SERVICE_ACCESS.SERVICE_DELETE); if (service != IntPtr.Zero) UninstallService(service); service = Win32.Service.CreateService(scm, "AMEPrepare", "AME Prepare", Win32.Service.ServiceAccessRights.AllAccess, Win32.Service.SERVICE_WIN32_OWN_PROCESS, Win32.Service.ServiceBootFlag.AutoStart, Win32.Service.ServiceError.Normal, $"\"{Win32.ProcessEx.GetCurrentProcessFileLocation()}\" --service {displayLastUsername}", null, IntPtr.Zero, null, null, null); if (service == IntPtr.Zero) throw new ApplicationException("Failed to install service."); AllowServiceSafeBoot(); Win32.Service.CloseServiceHandle(service); } finally { Win32.Service.CloseServiceHandle(scm); } } private static bool EnableDontDisplayLastUsername() { var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", true); if (key != null) { var value = key.GetValue("dontdisplaylastusername"); if (value is int intValue && intValue == 1) { return true; } key.SetValue("dontdisplaylastusername", 1, RegistryValueKind.DWord); } return false; } private static void AllowServiceSafeBoot() { var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\SafeBoot\Minimal", true); if (key != null) { var subKey = key.CreateSubKey("AMEPrepare"); subKey?.SetValue("", "Service", RegistryValueKind.String); } } public static void UninstallService(IntPtr service) { Win32.Service.SERVICE_STATUS status = new Win32.Service.SERVICE_STATUS(); Win32.Service.ControlService(service, Win32.Service.ServiceControl.Stop, status); var changedStatus = WaitForServiceStatus(service, Win32.Service.ServiceState.StopPending, Win32.Service.ServiceState.Stopped); if (!changedStatus) throw new ApplicationException("Unable to stop service"); if (!Win32.Service.DeleteService(service)) throw new ApplicationException("Could not delete service " + Marshal.GetLastWin32Error()); else Win32.Service.CloseServiceHandle(service); } private static bool WaitForServiceStatus(IntPtr service, Win32.Service.ServiceState waitStatus, Win32.Service.ServiceState desiredStatus) { Win32.Service.SERVICE_STATUS status = new Win32.Service.SERVICE_STATUS(); Win32.Service.QueryServiceStatus(service, status); if (status.dwCurrentState == desiredStatus) return true; int dwStartTickCount = Environment.TickCount; int dwOldCheckPoint = status.dwCheckPoint; while (status.dwCurrentState == waitStatus) { // Do not wait longer than the wait hint. A good interval is // one tenth the wait hint, but no less than 1 second and no // more than 10 seconds. int dwWaitTime = status.dwWaitHint / 10; if (dwWaitTime < 1000) dwWaitTime = 1000; else if (dwWaitTime > 10000) dwWaitTime = 10000; Thread.Sleep(dwWaitTime); // Check the status again. if (Win32.Service.QueryServiceStatus(service, status) == 0) break; if (status.dwCheckPoint > dwOldCheckPoint) { // The service is making progress. dwStartTickCount = Environment.TickCount; dwOldCheckPoint = status.dwCheckPoint; } else { if (Environment.TickCount - dwStartTickCount > status.dwWaitHint) { // No progress made within the wait hint break; } } } return (status.dwCurrentState == desiredStatus || status.dwCurrentState == Win32.Service.ServiceState.NotFound); } private static int RunPSCommand(string command, [CanBeNull] DataReceivedEventHandler outputHandler, [CanBeNull] DataReceivedEventHandler errorHandler) => RunCommand("powershell.exe", $"-NoP -C \"{command}\"", outputHandler, errorHandler); private static int RunCommand(string exe, string arguments, [CanBeNull] DataReceivedEventHandler outputHandler, [CanBeNull] DataReceivedEventHandler errorHandler) { var process = new Process { StartInfo = new ProcessStartInfo() { FileName = exe, Arguments = arguments, CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = outputHandler != null, RedirectStandardError = errorHandler != null } }; if (outputHandler != null) process.OutputDataReceived += outputHandler; if (errorHandler != null) process.ErrorDataReceived += errorHandler; process.Start(); if (outputHandler != null) process.BeginOutputReadLine(); if (errorHandler != null) process.BeginErrorReadLine(); process.WaitForExit(); return process.ExitCode; } private static string ExtractCab() { var cabArch = Win32.SystemInfoEx.SystemArchitecture == Architecture.Arm || Win32.SystemInfoEx.SystemArchitecture == Architecture.Arm64 ? "arm64" : "amd64"; var fileDir = Environment.ExpandEnvironmentVariables("%ProgramData%\\AME"); if (!Directory.Exists(fileDir)) Directory.CreateDirectory(fileDir); var destination = Path.Combine(fileDir, $"Z-AME-NoDefender-Package31bf3856ad364e35{cabArch}1.0.0.0.cab"); if (File.Exists(destination)) { return destination; } Assembly assembly = Assembly.GetEntryAssembly(); using (UnmanagedMemoryStream stream = (UnmanagedMemoryStream)assembly!.GetManifestResourceStream($"TrustedUninstaller.GUI.Resources.Z-AME-NoDefender-Package31bf3856ad364e35{cabArch}1.0.0.0.cab")) { byte[] buffer = new byte[stream!.Length]; stream.Read(buffer, 0, buffer.Length); File.WriteAllBytes(destination, buffer); } return destination; } [InterprocessMethod(Level.Administrator)] public static bool Disable() { bool restartRequired = true; foreach (var service in DefenderItems.Where(x => x.Value == ProcessType.Service || x.Value == ProcessType.Device).Select(x => x.Key)) { CoreActions.SafeRun(new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\" + service, Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD, Operation = RegistryValueOperation.Set }); } CoreActions.SafeRun(new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\SecurityHealthService", Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD }); CoreActions.SafeRun(new RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Policies\Microsoft\Windows\System", Value = "EnableSmartScreen", Data = 0, Type = RegistryValueType.REG_DWORD }); try { new RegistryValueAction() { KeyName = "HKLM\\SOFTWARE\\Microsoft\\Windows Defender", Value = "ProductAppDataPath", Operation = RegistryValueOperation.Delete }.RunTask(); new RegistryValueAction() { KeyName = "HKLM\\SOFTWARE\\Microsoft\\Windows Defender", Value = "InstallLocation", Operation = RegistryValueOperation.Delete }.RunTask(); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "Error removing Defender install values."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), 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 }.RunTask(); if (new RegistryValueAction() { KeyName = "HKLM\\SOFTWARE\\Microsoft\\Windows Defender", Value = "InstallLocation", Operation = RegistryValueOperation.Delete }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Could not remove defender install values."); } try { new RegistryKeyAction() { KeyName = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend" }.RunTask(); if (new RegistryKeyAction() { KeyName = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend" }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown reason"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First WinDefend service removal failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), 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 }.RunTask(); if (new RegistryKeyAction() { KeyName = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend" }.GetStatus() != UninstallTaskStatus.Completed) { Log.EnqueueSafe(LogType.Warning, "WinDefend service removal failed.", new SerializableTrace()); try { new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\WinDefend", Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\WinDefend", Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown reason"); } catch (Exception ex) { Log.EnqueueExceptionSafe(e, "First WinDefend disable failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), 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 }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\WinDefend", Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Could not disable WinDefend service."); } } } try { // MpOAV.dll is normally in use by a lot of processes. This prevents that. new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32" }.RunTask(); if (new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32" }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown reason"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First MpOAV mapping removal failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), 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 }.RunTask(); if (new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32" }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not remove MpOAV mapping.", new SerializableTrace()); } try { // smartscreenps.dll is sometimes in use by a lot of processes. This prevents that. new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\InprocServer32" }.RunTask(); // This may not be important. new RegistryKeyAction() { KeyName = @"HKCR\WOW6432Node\CLSID\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\InprocServer32" }.RunTask(); new RegistryKeyAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId\Windows.Internal.Security.SmartScreen.AppReputationService" }.RunTask(); new RegistryKeyAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId\Windows.Internal.Security.SmartScreen.EventLogger" }.RunTask(); new RegistryKeyAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId\Windows.Internal.Security.SmartScreen.UriReputationService" }.RunTask(); if (new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\InprocServer32" }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown reason"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First smartscreenps mapping removal failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), Exe = $"NSudoLC.exe", Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait cmd /c \"reg delete \"HKCR\\CLSID\\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\\InprocServer32\" /f &" + "reg delete \"HKCR\\WOW6432Node\\CLSID\\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\\InprocServer32\" /f &" + "reg delete \"HKLM\\SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\Windows.Internal.Security.SmartScreen.AppReputationService\" /f &" + "reg delete \"HKLM\\SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\Windows.Internal.Security.SmartScreen.EventLogger\" /f &" + "reg delete \"HKLM\\SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\Windows.Internal.Security.SmartScreen.UriReputationService\" /f\"", CreateWindow = false }.RunTask(); if (new RegistryKeyAction() { KeyName = @"HKCR\CLSID\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\InprocServer32" }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not remove smartscreenps mapping.", new SerializableTrace()); } try { // Can cause ProcessHacker driver warnings without this new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown error"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First memory integrity disable failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), Exe = $"NSudoLC.exe", Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity"" /v Enabled /d 0 /f", CreateWindow = false }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not disable memory integrity.", new SerializableTrace()); } try { // Can cause ProcessHacker driver warnings without this new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown error"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First memory integrity disable failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), Exe = $"NSudoLC.exe", Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity"" /v Enabled /d 0 /f", CreateWindow = false }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", Value = "Enabled", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not disable memory integrity.", new SerializableTrace()); } try { // Can cause ProcessHacker driver warnings without this new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) throw new Exception("Unknown error"); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "First blocklist disable failed."); new RunAction() { RawPath = Directory.GetCurrentDirectory(), Exe = $"NSudoLC.exe", Arguments = @"-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add ""HKLM\SYSTEM\CurrentControlSet\Control\CI\Config"" /v VulnerableDriverBlocklistEnable /d 0 /f", CreateWindow = false }.RunTask(); if (new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", Value = "VulnerableDriverBlocklistEnable", Data = 0, }.GetStatus() != UninstallTaskStatus.Completed) Log.EnqueueSafe(LogType.Warning, "Could not disable blocklist.", new SerializableTrace()); } return restartRequired; } public static void GetDefenderPrivileges() { ImpersonateProcessByName("winlogon", out Win32.TokensEx.SafeTokenHandle impersonatedTokenHandle); impersonatedTokenHandle.Dispose(); ImpersonateProcessByName("lsass", out impersonatedTokenHandle); impersonatedTokenHandle = CreateWinDefendToken(impersonatedTokenHandle, false); Win32.Tokens.ImpersonateLoggedOnUser(impersonatedTokenHandle); } [InterprocessMethod(Level.Administrator)] public static int StartElevatedProcess(string exe, string command) { ImpersonateProcessByName("winlogon", out Win32.TokensEx.SafeTokenHandle impersonatedTokenHandle); impersonatedTokenHandle.Dispose(); ImpersonateProcessByName("lsass", out impersonatedTokenHandle); impersonatedTokenHandle = CreateWinDefendToken(impersonatedTokenHandle, true); var process = new AugmentedProcess.Process(); process.StartInfo = new AugmentedProcess.ProcessStartInfo(exe, command) { UseShellExecute = false, CreateNoWindow = true }; process.Start(AugmentedProcess.Process.CreateType.UserToken, ref impersonatedTokenHandle); return process!.Id; } public enum ProcessType { Service = 1, Device = 2, Exe = 3, } public static bool Kill() { try { GetDefenderPrivileges(); CoreActions.SafeRun(new RegistryValueAction() { KeyName = $"HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender Security Center\\Notifications", Value = "DisableNotifications", Data = 1, Type = RegistryValueType.REG_DWORD }); CoreActions.SafeRun(new RegistryValueAction() { KeyName = @"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.SecurityAndMaintenance", Value = "Enabled", Data = 0, Type = RegistryValueType.REG_DWORD }); var services = ServiceController.GetServices(); var devices = ServiceController.GetDevices(); var stopped = new List(); var notStopped = new List(); foreach (var item in DefenderItems) { try { if (item.Value == ProcessType.Exe) { var process = Process.GetProcessesByName(item.Key).FirstOrDefault(); if (process != null) process.Kill(); continue; } var controller = item.Value == ProcessType.Service ? services.FirstOrDefault(x => x.ServiceName == item.Key) : devices.FirstOrDefault(x => x.ServiceName == item.Key); if (controller == null || controller.Status == ServiceControllerStatus.Stopped) continue; try { controller.Stop(); stopped.Add(controller); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "Service stop error."); notStopped.Add(controller); } } catch (Exception e) { Log.EnqueueExceptionSafe(e, "Error during service kill loop."); } } foreach (var controller in stopped) { try { controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(5)); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "Error waiting for service."); } } if (notStopped.Count > 0) { Thread.Sleep(1000); foreach (var controller in notStopped) { try { controller.Stop(); controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(7)); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "Service stop re-try error."); } } } if (Process.GetProcessesByName("MsMpEng").Any()) { Log.EnqueueSafe(LogType.Warning, "First Defender stop failed.", new SerializableTrace()); new RunAction() { RawPath = Directory.GetCurrentDirectory(), 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, }.RunTask(); } return !Process.GetProcessesByName("MsMpEng").Any(); } catch (Exception e) { Log.EnqueueExceptionSafe(e, "Unknown error."); return false; } } private static Win32.TokensEx.SafeTokenHandle CreateWinDefendToken(Win32.TokensEx.SafeTokenHandle handle, bool primary) { Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_ASSIGNPRIMARYTOKEN_NAME); Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_INCREASE_QUOTA_NAME); var privileges = new[] { Win32.Tokens.SE_INCREASE_QUOTA_NAME, Win32.Tokens.SE_MACHINE_ACCOUNT_NAME, Win32.Tokens.SE_SECURITY_NAME, Win32.Tokens.SE_TAKE_OWNERSHIP_NAME, Win32.Tokens.SE_LOAD_DRIVER_NAME, Win32.Tokens.SE_SYSTEM_PROFILE_NAME, Win32.Tokens.SE_SYSTEMTIME_NAME, Win32.Tokens.SE_PROFILE_SINGLE_PROCESS_NAME, Win32.Tokens.SE_INCREASE_BASE_PRIORITY_NAME, Win32.Tokens.SE_CREATE_PERMANENT_NAME, Win32.Tokens.SE_BACKUP_NAME, Win32.Tokens.SE_RESTORE_NAME, Win32.Tokens.SE_SHUTDOWN_NAME, Win32.Tokens.SE_DEBUG_NAME, Win32.Tokens.SE_AUDIT_NAME, Win32.Tokens.SE_SYSTEM_ENVIRONMENT_NAME, Win32.Tokens.SE_CHANGE_NOTIFY_NAME, Win32.Tokens.SE_UNDOCK_NAME, Win32.Tokens.SE_SYNC_AGENT_NAME, Win32.Tokens.SE_ENABLE_DELEGATION_NAME, Win32.Tokens.SE_MANAGE_VOLUME_NAME, Win32.Tokens.SE_IMPERSONATE_NAME, Win32.Tokens.SE_CREATE_GLOBAL_NAME, Win32.Tokens.SE_TRUSTED_CREDMAN_ACCESS_NAME, Win32.Tokens.SE_RELABEL_NAME, Win32.Tokens.SE_TIME_ZONE_NAME, Win32.Tokens.SE_CREATE_SYMBOLIC_LINK_NAME, Win32.Tokens.SE_DELEGATE_SESSION_USER_IMPERSONATE_NAME, Win32.Tokens.SE_ASSIGNPRIMARYTOKEN_NAME, Win32.Tokens.SE_REMOTE_SHUTDOWN_NAME, Win32.Tokens.SE_INCREASE_WORKING_SET_NAME, Win32.Tokens.SE_TCB_NAME, Win32.Tokens.SE_CREATE_PAGEFILE_NAME, Win32.Tokens.SE_LOCK_MEMORY_NAME, Win32.Tokens.SE_CREATE_TOKEN_NAME }; var authId = Win32.Tokens.SYSTEM_LUID; Win32.SID.SID_IDENTIFIER_AUTHORITY ntAuthority = new Win32.SID.SID_IDENTIFIER_AUTHORITY(); ntAuthority.Value = new byte[] { 0, 0, 0, 0, 0, Win32.SID.NtSecurityAuthority }; Win32.SID.AllocateAndInitializeSid(ref ntAuthority, 1, 18, 0, 0, 0, 0, 0, 0, 0, out Win32.SID.SafeSIDHandle pLocalSystem); Win32.Tokens.TOKEN_USER tokenUser = new Win32.Tokens.TOKEN_USER(); tokenUser.User.Sid = pLocalSystem.DangerousGetHandle(); tokenUser.User.Attributes = 0; var pTokenPrivileges = Win32.TokensEx.GetInfoFromToken(handle, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenPrivileges, Marshal.SizeOf()); var pTokenGroups = Win32.TokensEx.GetInfoFromToken(handle, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenGroups, Marshal.SizeOf()); var pTokenPrimaryGroup = Win32.TokensEx.GetInfoFromToken(handle, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenPrimaryGroup, Marshal.SizeOf()); var pTokenDefaultDacl = Win32.TokensEx.GetInfoFromToken(handle, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenDefaultDacl, Marshal.SizeOf()); var tokenPrivileges = Win32.TokensEx.CreateTokenPrivileges(privileges); var tokenGroups = (Win32.Tokens.TOKEN_GROUPS)Marshal.PtrToStructure( pTokenGroups, typeof(Win32.Tokens.TOKEN_GROUPS)); var tokenPrimaryGroup = (Win32.Tokens.TOKEN_PRIMARY_GROUP)Marshal.PtrToStructure(pTokenPrimaryGroup, typeof(Win32.Tokens.TOKEN_PRIMARY_GROUP)); var tokenDefaultDacl = (Win32.Tokens.TOKEN_DEFAULT_DACL)Marshal.PtrToStructure( pTokenDefaultDacl, typeof(Win32.Tokens.TOKEN_DEFAULT_DACL)); var tokenOwner = new Win32.Tokens.TOKEN_OWNER(pLocalSystem.DangerousGetHandle()); var tokenSource = new Win32.Tokens.TOKEN_SOURCE("*SYSTEM*") { SourceIdentifier = { LowPart = 0, HighPart = 0 } }; List requiredGroups = new List() { Win32.SID.DOMAIN_ALIAS_RID_ADMINS, Win32.SID.DOMAIN_ALIAS_RID_LOCAL_AND_ADMIN_GROUP, Win32.SID.TRUSTED_INSTALLER_RID, Win32.SID.NT_SERVICE_SID, "S-1-5-80-1913148863-3492339771-4165695881-2087618961-4109116736" }; List requiredSids = new List(); for (var idx = 0; idx < tokenGroups.GroupCount - 1; idx++) { Win32.SID.ConvertSidToStringSid(tokenGroups.Groups[idx].Sid, out string strSid); foreach (var requiredGroup in requiredGroups.ToArray()) { if (string.Compare(strSid, requiredGroup, StringComparison.OrdinalIgnoreCase) == 0) { tokenGroups.Groups[idx].Attributes &= ~(uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_USE_FOR_DENY_ONLY; tokenGroups.Groups[idx].Attributes |= (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_OWNER; requiredGroups.Remove(requiredGroup); } } } foreach (var requiredGroup in requiredGroups) { Win32.SID.ConvertStringSidToSid(requiredGroup, out Win32.SID.SafeSIDHandle sid); if (sid.IsInvalid) Log.EnqueueSafe(LogType.Warning, "Could not convert string SID to SID: " + requiredGroup, new SerializableTrace()); else { requiredSids.Add(sid); tokenGroups.Groups[tokenGroups.GroupCount].Sid = sid.DangerousGetHandle(); tokenGroups.Groups[tokenGroups.GroupCount].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_OWNER; tokenGroups.GroupCount++; } } var expirationTime = new Win32.LARGE_INTEGER() { QuadPart = -1L }; var sqos = new Win32.Tokens.SECURITY_QUALITY_OF_SERVICE( Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.SECURITY_STATIC_TRACKING, 0); var oa = new Win32.Tokens.OBJECT_ATTRIBUTES(string.Empty, 0) { }; var pSqos = Marshal.AllocHGlobal(Marshal.SizeOf(sqos)); Marshal.StructureToPtr(sqos, pSqos, true); oa.SecurityQualityOfService = pSqos; var status = Win32.Tokens.ZwCreateToken(out Win32.TokensEx.SafeTokenHandle elevatedToken, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, ref oa, Win32.Tokens.TOKEN_TYPE.TokenPrimary, ref authId, ref expirationTime, ref tokenUser, ref tokenGroups, ref tokenPrivileges, ref tokenOwner, ref tokenPrimaryGroup, ref tokenDefaultDacl, ref tokenSource); Win32.LocalFree(pTokenGroups); Win32.LocalFree(pTokenDefaultDacl); Win32.LocalFree(pTokenPrivileges); Win32.LocalFree(pTokenPrimaryGroup); requiredSids.ForEach(x => x.Dispose()); pLocalSystem.Dispose(); if (status != 0) throw new Win32Exception($"Error creating defender token: " + status); var sessionId = Process.GetCurrentProcess().SessionId; if (!Win32.Tokens.SetTokenInformation(elevatedToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, sizeof(int))) throw new Win32Exception("Error setting token session id: " + Marshal.GetLastWin32Error()); return elevatedToken; } private static void ImpersonateProcessByName(string name, out Win32.TokensEx.SafeTokenHandle handle) { using var process = Process.GetProcessesByName(name).First(); var processHandle = new SafeProcessHandle(process.Handle, false); Win32.Tokens.OpenProcessToken(processHandle, Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE | Win32.Tokens.TokenAccessFlags.TOKEN_ASSIGN_PRIMARY | Win32.Tokens.TokenAccessFlags.TOKEN_QUERY | Win32.Tokens.TokenAccessFlags.TOKEN_IMPERSONATE, out Win32.TokensEx.SafeTokenHandle tokenHandle); Win32.Tokens.DuplicateTokenEx(tokenHandle, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, Win32.Tokens.TOKEN_TYPE.TokenImpersonation, out handle); if (!Win32.Tokens.ImpersonateLoggedOnUser(handle)) throw new Win32Exception("Error impersonating token: " + Marshal.GetLastWin32Error()); tokenHandle.Dispose(); } } } ================================================ FILE: TrustedUninstaller.Shared/Exceptions/ErrorHandlingException.cs ================================================ using System; using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.Shared.Exceptions { public class ErrorHandlingException : Exception { public ErrorHandlingException(TaskAction.ExitCodeAction action, string message) => (Action, Message) = (action, message); public TaskAction.ExitCodeAction Action { get; set; } public new string Message { get; set; } } } ================================================ FILE: TrustedUninstaller.Shared/Exceptions/InvalidRegistryEntryException.cs ================================================ using System; namespace TrustedUninstaller.Shared.Exceptions { public class InvalidRegistryEntryException : Exception { public InvalidRegistryEntryException() { } public InvalidRegistryEntryException(string message) : base(message) { } public InvalidRegistryEntryException(string message, Exception inner) : base(message, inner) { } } } ================================================ FILE: TrustedUninstaller.Shared/Exceptions/TaskInProgressException.cs ================================================ using System; namespace TrustedUninstaller.Shared.Exceptions { public class TaskInProgressException : Exception { public TaskInProgressException() {} public TaskInProgressException(string message) : base(message) {} public TaskInProgressException(string message, Exception inner) : base(message, inner) {} } } ================================================ FILE: TrustedUninstaller.Shared/Globals.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Management; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Core; using Microsoft.Win32; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared { public class Globals { public const string CurrentVersion = "0.8.4"; public static readonly VersionNumber CurrentVersionNumber = VersionNumber.GetVersionNumber(CurrentVersion); } [Serializable] public class WizardMetadata : Log.ILogMetadata { public DateTime CreationTime { get; set; } public string ClientVersion { get; set; } public string WindowsVersion { get; set; } public string SystemLanguage { get; set; } public string UserLanguage { get; set; } public Architecture Architecture { get; set; } public string SystemMemory { get; set; } public int SystemThreads { get; set; } public virtual void Construct() { ClientVersion = Globals.CurrentVersion; WindowsVersion = $"Windows {Win32.SystemInfoEx.WindowsVersion.MajorVersion} {Win32.SystemInfoEx.WindowsVersion.Edition} {Win32.SystemInfoEx.WindowsVersion.BuildNumber}.{Win32.SystemInfoEx.WindowsVersion.UpdateNumber}"; SystemLanguage = Win32.SystemInfoEx.GetSystemLanguage(); UserLanguage = Win32.SystemInfoEx.GetUserLanguage(); SystemMemory = StringUtils.HumanReadableBytes(Win32.SystemInfoEx.GetSystemMemoryInBytes()); SystemThreads = Environment.ProcessorCount; Architecture = Win32.SystemInfoEx.SystemArchitecture; CreationTime = DateTime.UtcNow; } public string Serialize(ISerializer serializer) => serializer.Serialize(this); } } ================================================ FILE: TrustedUninstaller.Shared/ISO.cs ================================================ using System; using JetBrains.Annotations; namespace TrustedUninstaller.Shared { [Serializable] public class ISO { public string Name { get; set; } public string Creator { get; set; } public Guid? UniqueId { get; set; } = null; public string Version { get; set; } [CanBeNull] public string WindowsVersion { get; set; } [CanBeNull] public string WindowsUpdateVersion { get; set; } public string[] Options { get; set; } public bool HardwareRequirementsDisabled { get; set; } = false; public bool BitLockerDisabled { get; set; } = false; public bool InternetRequired { get; set; } = false; } } ================================================ FILE: TrustedUninstaller.Shared/NativeProcess.cs ================================================ using Microsoft.Win32.SafeHandles; using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.ServiceProcess; using Core; using Interprocess; namespace TrustedUninstaller.Shared { [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } // This also works with CharSet.Ansi as long as the calling function uses the same character set. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct STARTUPINFO { public Int32 cb; public string lpReserved; public string lpDesktop; public string lpTitle; public Int32 dwX; public Int32 dwY; public Int32 dwXSize; public Int32 dwYSize; public Int32 dwXCountChars; public Int32 dwYCountChars; public Int32 dwFillAttribute; public Int32 dwFlags; public Int16 wShowWindow; public Int16 cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct STARTUPINFOEX { public STARTUPINFO StartupInfo; public IntPtr lpAttributeList; } public class NativeProcess { [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CreateProcessW( string lpApplicationName, [MarshalAs(UnmanagedType.LPWStr)] string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UpdateProcThreadAttribute( IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool InitializeProcThreadAttributeList( IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); private const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000; private const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000; private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; [DllImport("kernel32.dll", SetLastError = true)] static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); const UInt32 INFINITE = 0xFFFFFFFF; const UInt32 WAIT_ABANDONED = 0x00000080; const UInt32 WAIT_OBJECT_0 = 0x00000000; const UInt32 WAIT_TIMEOUT = 0x00000102; [InterprocessMethod(Level.Administrator)] public static int StartProcessAsTI(string path, string arguments) { var controller = new ServiceController("TrustedInstaller"); if (controller.Status != ServiceControllerStatus.Running) { controller.Start(); controller.WaitForStatus(ServiceControllerStatus.Running); } var targetProcess = Process.GetProcessesByName("TrustedInstaller").FirstOrDefault(); if (targetProcess == null) throw new Exception("Could not find TrustedInstaller process."); var parent = targetProcess.Id; // Create a new process with a different parent process // https://stackoverflow.com/questions/10554913/how-to-call-createprocess-with-startupinfoex-from-c-sharp-and-re-parent-the-ch var pInfo = new PROCESS_INFORMATION(); var sInfoEx = new STARTUPINFOEX(); sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx); var lpValue = IntPtr.Zero; try { var lpSize = IntPtr.Zero; var success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize); if (success || lpSize == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error()); sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize); success = InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref lpSize); if (!success) throw new Win32Exception(Marshal.GetLastWin32Error()); var parentHandle = Process.GetProcessById(parent).Handle; // This value should persist until the attribute list is destroyed using the DeleteProcThreadAttributeList function lpValue = Marshal.AllocHGlobal(IntPtr.Size); Marshal.WriteIntPtr(lpValue, parentHandle); success = UpdateProcThreadAttribute( sInfoEx.lpAttributeList, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, lpValue, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero); if (!success) throw new Win32Exception(Marshal.GetLastWin32Error()); var pSec = new SECURITY_ATTRIBUTES(); var tSec = new SECURITY_ATTRIBUTES(); pSec.nLength = Marshal.SizeOf(pSec); tSec.nLength = Marshal.SizeOf(tSec); CreateProcessW(null, $"\"{path}\"{(String.IsNullOrEmpty(arguments) ? null : " " + arguments)}", ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW, IntPtr.Zero, null, ref sInfoEx, out pInfo); return pInfo.dwProcessId; } finally { // Free the attribute list if (sInfoEx.lpAttributeList != IntPtr.Zero) { DeleteProcThreadAttributeList(sInfoEx.lpAttributeList); Marshal.FreeHGlobal(sInfoEx.lpAttributeList); } Marshal.FreeHGlobal(lpValue); // Close process and thread handles if (pInfo.hProcess != IntPtr.Zero) { CloseHandle(pInfo.hProcess); } if (pInfo.hThread != IntPtr.Zero) { CloseHandle(pInfo.hThread); } } } } } ================================================ FILE: TrustedUninstaller.Shared/NtStatus.cs ================================================ namespace TrustedUninstaller.Shared { internal enum NtStatus { NT_SUCCESS, NT_INFORMATION, NT_WARNING, NT_ERROR, } } ================================================ FILE: TrustedUninstaller.Shared/OOBE.cs ================================================ using System; using System.Collections.Generic; using System.Xml.Serialization; using JetBrains.Annotations; namespace TrustedUninstaller.Shared { public class OOBESoftware { public string Name { get; set; } public string Title { get; set; } public string Description { get; set; } public bool Local { get; set; } = true; [CanBeNull] public string IconPath { get; set; } public bool? IsDefaultWebBrowser { get; set; } } [Serializable] public class OOBE { public enum InternetRequirementLevel { Request, Force } [CanBeNull] public string Username { get; set; } [CanBeNull] public string Password { get; set; } [CanBeNull] public string AdminPassword { get; set; } [CanBeNull] public InternetRequirementLevel? InternetRequirement { get; set; } = null; public bool AdminUserEnabled { get; set; } = false; public bool AutoLogon { get; set; } = false; public string[] Options { get; set; } public bool Verified { get; set; } public List BulletPoints { get; set; } = new List(); public List Software { get; set; } = new List(); } } ================================================ FILE: TrustedUninstaller.Shared/Parser/PlaybookParser.cs ================================================ #nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using Microsoft.Win32; using TrustedUninstaller.Shared.Actions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.TypeResolvers; using TaskAction = TrustedUninstaller.Shared.Actions.TaskAction; namespace TrustedUninstaller.Shared.Parser { public static class PlaybookParser { public static IDeserializer Deserializer { get; } = new DeserializerBuilder() .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTagMapping("!task", typeof(TaskAction)) .WithTagMapping("!file", typeof(FileAction)) .WithTagMapping("!service", typeof(ServiceAction)) .WithTagMapping("!registryKey", typeof(RegistryKeyAction)) .WithTagMapping("!registryValue", typeof(RegistryValueAction)) .WithTagMapping("!appx", typeof(AppxAction)) .WithTagMapping("!systemPackage", typeof(SystemPackageAction)) //.WithTagMapping("!lineInFile", typeof(LineInFileAction)) .WithTagMapping("!scheduledTask", typeof(ScheduledTaskAction)) //.WithTagMapping("!user", typeof(UserAction)) .WithTagMapping("!run", typeof(RunAction)) .WithTagMapping("!powerShell", typeof(PowerShellAction)) //.WithTagMapping("!shortcut", typeof(ShortcutAction)) .WithTagMapping("!cmd", typeof(CmdAction)) .WithTagMapping("!taskKill", typeof(TaskKillAction)) .WithTagMapping("!software", typeof(SoftwareAction)) .WithTagMapping("!download", typeof(DownloadAction)) //.WithTagMapping("!update", typeof(UpdateAction)) .WithTagMapping("!writeStatus", typeof(WriteStatusAction)) .WithTagMapping("!status", typeof(WriteStatusAction)) .WithNodeTypeResolver(new TaskActionResolver()) .Build(); /* private ISerializer Serializer { get; } = new SerializerBuilder() .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTagMapping("!file", typeof(FileAction)) .WithTagMapping("!service", typeof(ServiceAction)) .WithTagMapping("!registryKey", typeof(RegistryKeyAction)) .WithTagMapping("!registryValue", typeof(RegistryValueAction)) .WithTagMapping("!appx", typeof(AppxAction)) .WithTagMapping("!systemPackage", typeof(SystemPackageAction)) .WithTagMapping("!lineInFile", typeof(LineInFileAction)) .WithTagMapping("!scheduledTask", typeof(ScheduledTaskAction)) .WithTagMapping("!user", typeof(UserAction)) .WithTagMapping("!run", typeof(RunAction)) .WithTagMapping("!powerShell", typeof(PowerShellAction)) .WithTagMapping("!shortcut", typeof(ShortcutAction)) .WithTagMapping("!cmd", typeof(CmdAction)) .WithTagMapping("!uninstallTask", typeof(UninstallTask)) .WithTagMapping("!taskKill", typeof(TaskKillAction)) .WithTagMapping("!update", typeof(UpdateAction)) .WithTagMapping("!writeStatus", typeof(WriteStatusAction)) .WithTypeResolver(new DynamicTypeResolver()) .EnsureRoundtrip() .Build(); */ } } ================================================ FILE: TrustedUninstaller.Shared/Parser/TaskActionResolver.cs ================================================  using System; using TrustedUninstaller.Shared.Actions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; using TaskAction = TrustedUninstaller.Shared.Actions.TaskAction; namespace TrustedUninstaller.Shared.Parser { internal class TaskActionResolver : INodeTypeResolver { public bool Resolve(NodeEvent? nodeEvent, ref Type currentType) { if (!currentType.IsInterface || currentType != typeof(ITaskAction)) { return false; } switch (nodeEvent?.Tag.Value) { case "!file:": currentType = typeof(FileAction); return true; case "!service:": currentType = typeof(ServiceAction); return true; //case "!user:": // currentType = typeof(UserAction); // return true; case "!run:": currentType = typeof(RunAction); return true; case "!powerShell:": currentType = typeof(PowerShellAction); return true; //case "!shortcut:": // currentType = typeof(ShortcutAction); // return true; case "!cmd:": currentType = typeof(CmdAction); return true; case "!scheduledTask:": currentType = typeof(ScheduledTaskAction); return true; //case "!lineInFile:": // currentType = typeof(LineInFileAction); // return true; case "!registryKey:": currentType = typeof(RegistryKeyAction); return true; case "!registryValue:": currentType = typeof(RegistryValueAction); return true; case "!appx:": currentType = typeof(AppxAction); return true; case "!systemPackage:": currentType = typeof(SystemPackageAction); return true; case "!taskKill:": currentType = typeof(TaskKillAction); return true; case "!software:": currentType = typeof(SoftwareAction); return true; case "!download:": currentType = typeof(DownloadAction); return true; //case "!update:": // currentType = typeof(UpdateAction); // return true; case "!writeStatus:": currentType = typeof(WriteStatusAction); return true; case "!status:": currentType = typeof(WriteStatusAction); return true; case "!task:": currentType = typeof(TaskAction); return true; default: return false; } } } } ================================================ FILE: TrustedUninstaller.Shared/Playbook.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Security.Policy; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Xml; using System.Xml.Linq; using System.Xml.Schema; using System.Xml.Serialization; using Core; using Core.Miscellaneous; using JetBrains.Annotations; using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using MessageBox = System.Windows.MessageBox; namespace TrustedUninstaller.Shared { [Serializable] public class VersionNumber : IXmlSerializable { public void WriteXml(XmlWriter writer) { writer.WriteValue(this.ToString()); } public void ReadXml(XmlReader reader) { var text = (string)reader.ReadElementContentAs(typeof(string), null); if (!String.IsNullOrEmpty(text)) { var version = GetVersionNumber(text); this.Major = version.Major; this.Minor = version.Minor; this.Revision = version.Revision; } } public XmlSchema GetSchema() => null; public int Major { get; set; } public int Minor { get; set; } public int Revision { get; set; } public static bool operator ==(VersionNumber a, VersionNumber b) { if (a is null || b is null) return true; return a.IsEqual(b); } public static bool operator !=(VersionNumber a, VersionNumber b) { if (a is null || b is null) return false; return !a.IsEqual(b); } public static bool operator >=(VersionNumber a, VersionNumber b) { if (a is null || b is null) throw new ArgumentNullException(); return a.IsGreaterThanOrEqualTo(b); } public static bool operator >(VersionNumber a, VersionNumber b) { if (a is null || b is null) throw new ArgumentNullException(); return a.IsGreaterThan(b); } public static bool operator <=(VersionNumber a, VersionNumber b) { if (a is null || b is null) throw new ArgumentNullException(); return a.IsLessThanOrEqualTo(b); } public static bool operator <(VersionNumber a, VersionNumber b) { if (a is null || b is null) throw new ArgumentNullException(); return a.IsLessThan(b); } public bool IsEqual(VersionNumber other) => Major == other.Major && Minor == other.Minor && Revision == other.Revision; public bool IsGreaterThan(VersionNumber other) => Major > other.Major || (Major >= other.Major && Minor > other.Minor) || (Major >= other.Major && Minor >= other.Minor && Revision > other.Revision); public bool IsLessThan(VersionNumber other) => other.IsGreaterThan(this); public bool IsGreaterThanOrEqualTo(VersionNumber other) => IsGreaterThan(other) || IsEqual(other); public bool IsLessThanOrEqualTo(VersionNumber other) => IsLessThan(other) || IsEqual(other); public override bool Equals(object obj) { if (obj is VersionNumber other) return this == other; return false; } public override string ToString() => Major + "." + Minor + "." + Revision; public static VersionNumber GetVersionNumber(string toBeParsed) { // Examples: // 0.4 // 0.4 Alpha // 1.0.5 // 1.0.5 Beta VersionNumber number = new VersionNumber(); // Remove characters after first space (and the space itself) if (toBeParsed.IndexOf(' ') >= 0) toBeParsed = toBeParsed.Substring(0, toBeParsed.IndexOf(' ')); var numbers = toBeParsed.Split('.'); if (numbers.Length <= 0) throw new XmlException($"Invalid version number '{toBeParsed}'"); for (var i = 0; i < numbers.Length; i++) { if (i == 0) number.Major = int.Parse(numbers[i], CultureInfo.InvariantCulture); if (i == 1) number.Minor = int.Parse(numbers[i], CultureInfo.InvariantCulture); if (i == 2) number.Revision = int.Parse(numbers[i], CultureInfo.InvariantCulture); if (i > 2) throw new Exception("Version number invalid."); } return number; } } public enum ErrorLevel { Success = 0, Error = 1, FatalError = 2, } public class Playbook : XmlDeserializable { public double MeasureStringWidth(string candidate, Typeface typeface, double fontSize) { var formattedText = new FormattedText( candidate, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeface, fontSize, Brushes.Black, 1); return formattedText.Width; } public override void Validate() { if (UniqueId != null && UniqueId == Guid.Empty) throw new XmlException("UniqueId must be unique, currently it is not unique. Use an online UUIDv4 generator to create a new unique ID."); if (Name.Any(x => System.IO.Path.GetInvalidFileNameChars().Contains(x))) throw new XmlException(@"Playbook Name cannot contain invalid file name characters including: \ / : * ? < > |"); if (MeasureStringWidth(Name, new Typeface("Segoe UI"), 14.5) > 97) throw new XmlException("Playbook Name is too long."); if (MeasureStringWidth(Username, new Typeface("Segoe UI"), 13) > 100) throw new XmlException("Playbook Username is too long."); if (Wrap.ExecuteSafe(() => VersionNumber.GetVersionNumber(Version), false).Failed) throw new XmlException($"Improper version format '{Version}'. Version must follow one of these formats:\r\n1\r\n1.0\r\n1.0.0"); if (SupportsISO) { if (OOBE == null || OOBE.BulletPoints == null || OOBE.BulletPoints.Count == 0) throw new XmlException("OOBE BulletPoints must be specified when SupportsISO is true."); if (OOBE.BulletPoints == null || OOBE.BulletPoints.Count != 3) throw new XmlException("There must be exactly three features under OOBEFeatures."); else if (OOBE.BulletPoints.Any(x => OOBE.BulletPoints.Any(y => y != x && y.Icon == x.Icon))) throw new XmlException("OOBE feature icons must be distinct."); else if (OOBE.BulletPoints.Any(x => string.IsNullOrEmpty(x.Title) || string.IsNullOrWhiteSpace(x.Description))) throw new XmlException("OOBE feature title and description must be non-empty."); } IsUpgradeApplicable("1.0.0"); } [CanBeNull] public ISOSettings ISO { get; set; } = null; [CanBeNull] public OOBESettings OOBE { get; set; } = null; [XmlRequired(false)] public string Name { get; set; } [XmlRequired(false)] public string ShortDescription { get; set; } [XmlRequired(false)] public string Description { get; set; } [XmlRequired(false)] public string Title { get; set; } [XmlRequired(false)] public string Username { get; set; } public string Details { get; set; } [XmlRequired(false)] public string Version { get; set; } [XmlArray] [XmlArrayItem(Type = typeof(CheckboxPage))] [XmlArrayItem(Type = typeof(RadioPage))] [XmlArrayItem(Type = typeof(RadioImagePage))] public FeaturePage[] FeaturePages { get; set; } public Package[] Software { get; set; } = Array.Empty(); 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; } = new Requirements.Requirement[] {}; public string? InstallGuide { get; set; } 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; } public Guid? UniqueId { get; set; } [XmlAllowInlineArrayItem] public string[]? UpgradableFrom { get; set; } public bool? AllowUnsupportedUpgrades { get; set; } = true; #nullable disable public bool Overhaul { get; set; } = false; public bool SupportsISO { get; set; } = false; [XmlIgnore] public string Path { get; set; } public bool? UseKernelDriver { get; set; } = null; [XmlIgnore] public List Options { get; set; } = null; // Used for applied Playbooks [XmlIgnore] public ErrorLevel ErrorLevel = 0; [XmlIgnore] public string[] SelectedOptions = new string[] {}; [XmlIgnore] public string[] AvailableOptions = new string[] {}; [XmlIgnore] public DateTime AppliedTimeUTC = new DateTime(); [XmlIgnore] [CanBeNull] public byte[] ImageBytes = null; [NotNull] public static Playbook[] GetAppliedPlaybooks() { var list = new List(); using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\AME\Playbooks\Applied"); if (key != null) { foreach (var subKeyName in key.GetSubKeyNames()) { try { var guid = subKeyName.Trim(new[] { '{', '}' }); if (Guid.TryParse(guid, out Guid uniqueId)) { using var subKey = key.OpenSubKey(subKeyName)!; var result = new Playbook(); result.UniqueId = uniqueId; result.Version = (string)subKey.GetValue("Version"); result.Name = (string)subKey.GetValue("Name"); result.Username = (string)subKey.GetValue("Username"); result.Overhaul = unchecked((int)Convert.ToUInt32(subKey.GetValue("Overhaul"))) == 1; result.ErrorLevel = (ErrorLevel)unchecked((int)Convert.ToUInt32(subKey.GetValue("ErrorLevel"))); result.AvailableOptions = (string[])subKey.GetValue("AvailableOptions"); result.SelectedOptions = (string[])subKey.GetValue("SelectedOptions"); result.AppliedTimeUTC = DateTime.FromBinary((long)subKey.GetValue("AppliedTimeUTC")); result.ImageBytes = (byte[])subKey.GetValue("Image"); list.Add(result); } } catch (Exception e) { } } } var appliedDir = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AME\AppliedPlaybooks"); if (Directory.Exists(appliedDir)) { foreach (var appliedPB in Directory.GetDirectories(appliedDir).Reverse()) { try { var result = AmeliorationUtil.DeserializePlaybook(appliedPB); if (File.Exists(System.IO.Path.Combine(appliedPB, "playbook.png")) && File.Exists(System.IO.Path.Combine(appliedPB, "verified.txt"))) result.ImageBytes = File.ReadAllBytes(System.IO.Path.Combine(appliedPB, "playbook.png")); if (File.Exists(System.IO.Path.Combine(appliedPB, "errors.txt"))) result.ErrorLevel = ErrorLevel.Error; result.Path = appliedPB; list.Add(result); } catch { } } } return list.ToArray(); } [CanBeNull] public virtual Playbook LastAppliedMatch([NotNull] IEnumerable appliedPlaybooks) { Playbook idMatch = null, userMatch = null; foreach (var item in appliedPlaybooks) { if (UniqueId == item.UniqueId) { idMatch = item; break; } else if (userMatch == null && Name == item.Name && Username == item.Username) userMatch = item; } return idMatch ?? userMatch; } public bool IsUpgradeApplicable(string oldVersion) { if (UpgradableFrom != null) { var oldVersionNumber = VersionNumber.GetVersionNumber(oldVersion); foreach (var version in UpgradableFrom) { if (version == "any") return true; if (version.Contains('-')) { var split = version.Split('-'); if (split.Length != 2) throw new XmlException($"Invalid version range format '{version}'"); var version1 = VersionNumber.GetVersionNumber(split[0]); var version2 = VersionNumber.GetVersionNumber(split[1]); if (oldVersionNumber.IsGreaterThanOrEqualTo(version1) && oldVersionNumber.IsLessThanOrEqualTo(version2)) return true; continue; } var versionNumberResult = Wrap.ExecuteSafe(() => VersionNumber.GetVersionNumber(version)); if (versionNumberResult.Failed) throw new XmlException($"Invalid '{nameof(UpgradableFrom)}' value '{version}'. Values must follow one of the these formats:\r\n1.0.0\r\n1.0.0-2.0.0\r\nany"); if (versionNumberResult.Value == oldVersionNumber) return true; } } return false; } public VersionNumber GetVersionNumber() { return VersionNumber.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); var tag = (string)array.FirstOrDefault()?["tag_name"]; return tag?.TrimStart('v'); } 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"])?.TrimStart('v')); 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}."; } public class CheckboxPage : FeaturePage { public override void Validate() { if (Options.Length > 2 && TopLine != null && BottomLine != null) throw new Exception(@$"CheckboxPage with a TopLine and BottomLine must not have more than 2 options."); if (Options.Length > 3 && (TopLine != null || BottomLine != null)) throw new Exception(@$"CheckboxPage with a TopLine or BottomLine must not have more than 3 options."); if (Options.Length > 4) throw new Exception(@$"CheckboxPage must not have more than 4 options."); if (Options.Distinct().Count() != Options.Length) throw new XmlException("Duplicate options found in CheckboxPage."); } public class CheckboxOption : Option { [XmlAttribute] public bool IsChecked { get; set; } = true; [XmlAttribute] public bool IsEnabled { get; set; } = true; } [XmlArray] [XmlArrayItem(ElementName = "CheckboxOption", Type = typeof(CheckboxOption))] public override Option[] Options { get; set; } } public class RadioPage : FeaturePage { public override void Validate() { if (Options.Length > 2 && TopLine != null && BottomLine != null) throw new XmlException(@$"RadioPage with a TopLine and BottomLine must not have more than 2 options."); if (Options.Length > 3 && (TopLine != null || BottomLine != null)) throw new XmlException(@$"RadioPage with a TopLine or BottomLine must not have more than 3 options."); if (Options.Length > 4) throw new XmlException(@$"RadioPage must not have more than 4 options."); if (DefaultOption != null && !Options.Any(x => x.Name == DefaultOption)) throw new XmlException(@$"No option matching DefaultOption {DefaultOption} in Radio"); if (Options.Distinct().Count() != Options.Length) throw new XmlException("Duplicate options found in RadioPage."); } [XmlAttribute] public string DefaultOption { get; set; } = null; public class RadioOption : Option { } [XmlArray] [XmlArrayItem(ElementName = "RadioOption", Type = typeof(RadioOption))] public override Option[] Options { get; set; } [CanBeNull] public override string[] OptionNames() => Options?.Select(x => x.Name).ToArray(); } public class RadioImagePage : FeaturePage { public override void Validate() { if (Options.Length > 4) throw new XmlException(@$"RadioImagePage must not have more than 4 options."); if (DefaultOption != null && !Options.Any(x => x.Name == DefaultOption)) throw new XmlException(@$"No option matching DefaultOption {DefaultOption} in RadioImagePage."); if (Options.Distinct().Count() != Options.Length) throw new XmlException("Duplicate options found in RadioImagePage."); if (Options.OfType().Any(x => x.GradientTopColor == x.GradientBottomColor && !x.None)) throw new XmlException("RadioImageOption gradient colors must not be the same."); if (Options.OfType().Any(x => !x.None && (x.GradientTopColor.Length != 7 || x.GradientBottomColor.Length != 7))) throw new XmlException("RadioImageOption gradient colors must be in the format #RRGGBB."); if (Options.OfType().Any(x => string.Equals(x.GradientTopColor, "#FFFFFF", StringComparison.OrdinalIgnoreCase) || string.Equals(x.GradientBottomColor, "#FFFFFF", StringComparison.OrdinalIgnoreCase) || string.Equals(x.GradientTopColor, "#000000", StringComparison.OrdinalIgnoreCase) || string.Equals(x.GradientBottomColor, "#000000", StringComparison.OrdinalIgnoreCase))) throw new XmlException("RadioImageOption gradient colors must not be black or white."); } [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(Type = typeof(RadioImageOption))] public override Option[] Options { get; set; } [CanBeNull] public override string[] OptionNames() => Options?.Select(x => x.Name).ToArray(); [XmlAttribute] public bool CheckDefaultBrowser { get; set; } = false; } public abstract class FeaturePage : XmlDeserializable { public override void Validate() => throw new Exception("FeaturePage cannot be used directly. Use CheckboxPage, RadioPage, or RadioImagePage instead."); [XmlAttribute] public string DependsOn { get; set; } = null; [XmlAttribute] public string WindowsVersion { 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; [XmlAttribute] public string WindowsVersion { get; set; } = null; } public class Line { [XmlAttribute("Text")] public string Text { get; set; } [XmlAttribute("Link")] public string Link { get; set; } = null; } [XmlArray] [XmlArrayItem(Type = typeof(Option))] public virtual Option[] Options { get; set; } [CanBeNull] public virtual string[] OptionNames() => Options?.Select(x => x.Name).ToArray(); [XmlAttribute] public string Description { get; set; } } public class Package : XmlDeserializable { public override void Validate() { if (string.IsNullOrWhiteSpace(Name)) throw new XmlException("Software must have a Name."); if (string.IsNullOrWhiteSpace(Title)) throw new XmlException("Software must have a Title."); if (string.IsNullOrWhiteSpace(Description)) throw new XmlException("Software must have a Description."); if (string.IsNullOrWhiteSpace(Icon)) throw new XmlException("Software must have an Icon specified."); } [XmlAttribute] public string Option { get; set; } = null; [XmlAttribute] public bool Local { get; set; } = true; [XmlAttribute] public bool? DefaultWebBrowser { get; set; } = null; public string Name { get; set; } public string Title { get; set; } public string Description { get; set; } public string Icon { get; set; } } } public enum ComponentIcon { Rocket, Privacy, Lock, } public class ISOSettings { public bool DisableBitLocker { get; set; } = false; public bool DisableHardwareRequirements { get; set; } = false; } public class OOBESettings { public OOBE.InternetRequirementLevel? Internet { get; set; } = null; public List BulletPoints { get; set; } = null; } public class BulletPoint { [XmlAttribute] public ComponentIcon Icon { get; set; } [XmlAttribute] public string Title { get; set; } [XmlAttribute] public string Description { get; set; } } } ================================================ FILE: TrustedUninstaller.Shared/Predicates/IPredicate.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TrustedUninstaller.Shared.Predicates { interface IPredicate { public Task Evaluate(); } } ================================================ FILE: TrustedUninstaller.Shared/ProcessPrivilege.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using Core; using Microsoft.Win32.SafeHandles; using TrustedUninstaller.Shared; namespace TrustedUninstaller.Shared.Actions { public enum Privilege { TrustedInstaller, System, CurrentUserTrustedInstaller, CurrentUserElevated, CurrentUser, } public class ProcessPrivilege { private static Win32.TokensEx.SafeTokenHandle userToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); private static Win32.TokensEx.SafeTokenHandle elevatedUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); private static Win32.TokensEx.SafeTokenHandle trustedInstallerUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); private static Win32.TokensEx.SafeTokenHandle systemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); private static Win32.TokensEx.SafeTokenHandle impsersonatedSystemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); private static Win32.TokensEx.SafeTokenHandle lsassToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); internal static void ResetTokens() { userToken.Dispose(); elevatedUserToken.Dispose(); trustedInstallerUserToken.Dispose(); systemToken.Dispose(); impsersonatedSystemToken.Dispose(); lsassToken.Dispose(); elevatedUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); lsassToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); trustedInstallerUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); systemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); impsersonatedSystemToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); userToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); } private static readonly object _startLock = new object(); public static void StartPrivilegedTask(AugmentedProcess.Process process, Privilege privilege) { lock (_startLock) { var tcs = StartThread(process, privilege); tcs.Task.Wait(); for (int i = 0; tcs.Task.Result != null && i <= 3; i++) { Log.EnqueueExceptionSafe(LogType.Warning, tcs.Task.Result, "Error launching privileged process.", ("ExePath", Path.GetFileName(process.StartInfo.FileName))); ResetTokens(); Thread.Sleep(500 * i); tcs = StartThread(process, privilege); tcs.Task.Wait(); } if (tcs.Task.Result != null) throw new SecurityException("Error launching privileged process.", tcs.Task.Result); } } private static TaskCompletionSource StartThread(AugmentedProcess.Process process, Privilege privilege) { var tcs = new TaskCompletionSource(); var thread = new Thread(() => { try { switch (privilege) { case (Privilege.System): GetSystemToken(); process.Start(AugmentedProcess.Process.CreateType.UserToken, ref systemToken); break; case (Privilege.CurrentUser): GetUserToken(true); process.Start(AugmentedProcess.Process.CreateType.UserToken, ref userToken); break; case (Privilege.CurrentUserElevated): GetElevatedUserToken(false); process.Start(AugmentedProcess.Process.CreateType.UserToken, ref elevatedUserToken); break; case (Privilege.CurrentUserTrustedInstaller): GetElevatedUserToken(true); process.Start(AugmentedProcess.Process.CreateType.UserToken, ref trustedInstallerUserToken); break; default: throw new ArgumentException("Unexpected."); } } catch (Exception e) { tcs.SetResult(e); return; } tcs.SetResult(null); }); thread.Start(); return tcs; } private static uint GetUserSession() { var currentSessionId = (uint)Process.GetCurrentProcess().SessionId; uint sessionId = Wrap.ExecuteSafe(() => { sessionId = Win32.WTS.WTSGetActiveConsoleSessionId(); if (sessionId != 0xFFFFFFFF) { bool success = Win32.WTS.WTSQuerySessionInformation(IntPtr.Zero, sessionId, Win32.WTS.WTS_INFO_CLASS.WTSConnectState, out IntPtr buffer, out int returned); if (success && Marshal.ReadInt32(buffer) == 0) return sessionId; } return 0xFFFFFFFF; }, 0xFFFFFFFF).Value; if (sessionId != 0xFFFFFFFF && sessionId == currentSessionId) return sessionId; IntPtr pSessionInfo = IntPtr.Zero; Int32 count = 0; if (Win32.WTS.WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref pSessionInfo, ref count) == 0) { Log.EnqueueExceptionSafe(new Win32Exception(Marshal.GetLastWin32Error(), "Error enumerating user sessions.")); return currentSessionId; } Int32 dataSize = Marshal.SizeOf(typeof(Win32.WTS.WTS_SESSION_INFO)); Int64 current = (Int64)pSessionInfo; for (int i = 0; i < count; i++) { Win32.WTS.WTS_SESSION_INFO si = (Win32.WTS.WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(Win32.WTS.WTS_SESSION_INFO)); current += dataSize; if (si.State == Win32.WTS.WTS_CONNECTSTATE_CLASS.WTSActive) { sessionId = (uint)si.SessionID; if (sessionId == currentSessionId) break; } } Win32.WTS.WTSFreeMemory(pSessionInfo); return sessionId; } private static void GetUserToken(bool getPrivileges) { if (getPrivileges) { GetSystemToken(); var result = Win32.Tokens.ImpersonateLoggedOnUser(systemToken); if (!result) throw new Win32Exception(Marshal.GetLastWin32Error(), "Error impersonating system process token."); Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_ASSIGNPRIMARYTOKEN_NAME); Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_INCREASE_QUOTA_NAME); } if (!userToken.IsInvalid) return; var sessionId = GetUserSession(); if (Win32.WTS.WTSQueryUserToken(sessionId, out Win32.TokensEx.SafeTokenHandle wtsToken)) { if (!Win32.Tokens.DuplicateTokenEx(wtsToken, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.TOKEN_TYPE.TokenPrimary, out userToken)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate process token for lsass."); } return; } throw new Win32Exception("Error fetching active user session token: " + Marshal.GetLastWin32Error()); } private static void GetElevatedUserToken(bool trustedInstaller) { GetSystemToken(); var result = Win32.Tokens.ImpersonateLoggedOnUser(systemToken); if (!result) throw new Win32Exception(Marshal.GetLastWin32Error(), "Error impersonating system process token."); if (lsassToken.IsInvalid) { using var processHandle = Win32.Process.OpenProcess(Win32.Process.ProcessAccessFlags.QueryLimitedInformation, false, Process.GetProcessesByName("lsass").First().Id); if (!Win32.Tokens.OpenProcessToken(processHandle, Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE | Win32.Tokens.TokenAccessFlags.TOKEN_ASSIGN_PRIMARY | Win32.Tokens.TokenAccessFlags.TOKEN_QUERY | Win32.Tokens.TokenAccessFlags.TOKEN_IMPERSONATE, out var tokenHandle)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token for lsass."); } if (!Win32.Tokens.DuplicateTokenEx(tokenHandle, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, Win32.Tokens.TOKEN_TYPE.TokenImpersonation, out lsassToken)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate process token for lsass."); } } result = Win32.Tokens.ImpersonateLoggedOnUser(lsassToken); if (!result) throw new Win32Exception(Marshal.GetLastWin32Error(), "Error impersonating lsass process token: " + Marshal.GetLastWin32Error()); Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_ASSIGNPRIMARYTOKEN_NAME); Win32.TokensEx.AdjustCurrentPrivilege(Win32.Tokens.SE_INCREASE_QUOTA_NAME); if ((trustedInstaller ? !trustedInstallerUserToken.IsInvalid : !elevatedUserToken.IsInvalid)) return; var privileges = new[] { Win32.Tokens.SE_INCREASE_QUOTA_NAME, Win32.Tokens.SE_MACHINE_ACCOUNT_NAME, Win32.Tokens.SE_SECURITY_NAME, Win32.Tokens.SE_TAKE_OWNERSHIP_NAME, Win32.Tokens.SE_LOAD_DRIVER_NAME, Win32.Tokens.SE_SYSTEM_PROFILE_NAME, Win32.Tokens.SE_SYSTEMTIME_NAME, Win32.Tokens.SE_PROFILE_SINGLE_PROCESS_NAME, Win32.Tokens.SE_INCREASE_BASE_PRIORITY_NAME, Win32.Tokens.SE_CREATE_PERMANENT_NAME, Win32.Tokens.SE_BACKUP_NAME, Win32.Tokens.SE_RESTORE_NAME, Win32.Tokens.SE_SHUTDOWN_NAME, Win32.Tokens.SE_DEBUG_NAME, Win32.Tokens.SE_AUDIT_NAME, Win32.Tokens.SE_SYSTEM_ENVIRONMENT_NAME, Win32.Tokens.SE_CHANGE_NOTIFY_NAME, Win32.Tokens.SE_UNDOCK_NAME, Win32.Tokens.SE_SYNC_AGENT_NAME, Win32.Tokens.SE_ENABLE_DELEGATION_NAME, Win32.Tokens.SE_MANAGE_VOLUME_NAME, Win32.Tokens.SE_IMPERSONATE_NAME, Win32.Tokens.SE_CREATE_GLOBAL_NAME, Win32.Tokens.SE_TRUSTED_CREDMAN_ACCESS_NAME, Win32.Tokens.SE_RELABEL_NAME, Win32.Tokens.SE_TIME_ZONE_NAME, Win32.Tokens.SE_CREATE_SYMBOLIC_LINK_NAME, Win32.Tokens.SE_DELEGATE_SESSION_USER_IMPERSONATE_NAME, Win32.Tokens.SE_ASSIGNPRIMARYTOKEN_NAME, Win32.Tokens.SE_REMOTE_SHUTDOWN_NAME, Win32.Tokens.SE_INCREASE_WORKING_SET_NAME, Win32.Tokens.SE_TCB_NAME, Win32.Tokens.SE_CREATE_PAGEFILE_NAME, Win32.Tokens.SE_LOCK_MEMORY_NAME, Win32.Tokens.SE_CREATE_TOKEN_NAME }; var authId = Win32.Tokens.SYSTEM_LUID; GetUserToken(false); Win32.Tokens.DuplicateTokenEx(userToken, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.TOKEN_TYPE.TokenPrimary, out Win32.TokensEx.SafeTokenHandle dupedUserToken); Win32.SID.AllocateAndInitializeSid( ref Win32.SID.SECURITY_MANDATORY_LABEL_AUTHORITY, 1, trustedInstaller ? (int)Win32.SID.SECURITY_MANDATORY_LABEL.System : (int)Win32.SID.SECURITY_MANDATORY_LABEL.High, 0, 0, 0, 0, 0, 0, 0, out Win32.SID.SafeSIDHandle integritySid); using (integritySid) { var tokenMandatoryLabel = new Win32.Tokens.TOKEN_MANDATORY_LABEL() { Label = default(Win32.SID.SID_AND_ATTRIBUTES) }; tokenMandatoryLabel.Label.Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_INTEGRITY; tokenMandatoryLabel.Label.Sid = integritySid.DangerousGetHandle(); var integritySize = Marshal.SizeOf(tokenMandatoryLabel); var tokenInfo = Marshal.AllocHGlobal(integritySize); Marshal.StructureToPtr(tokenMandatoryLabel, tokenInfo, false); Win32.Tokens.SetTokenInformation( dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, tokenInfo, integritySize + Win32.SID.GetLengthSid(integritySid)); } var pTokenUser = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenUser, Marshal.SizeOf()); var pTokenOwner = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenOwner, Marshal.SizeOf()); var pTokenPrivileges = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenPrivileges, Marshal.SizeOf()); var pTokenGroups = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenGroups, Marshal.SizeOf()); var pTokenPrimaryGroup = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenPrimaryGroup, Marshal.SizeOf()); var pTokenDefaultDacl = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenDefaultDacl, Marshal.SizeOf()); var pTokenSource = Win32.TokensEx.GetInfoFromToken(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSource, Marshal.SizeOf()); var tokenUser = (Win32.Tokens.TOKEN_USER)Marshal.PtrToStructure(pTokenUser, typeof(Win32.Tokens.TOKEN_USER)); var tokenPrivileges = trustedInstaller ? Win32.TokensEx.CreateTokenPrivileges(privileges) : Win32.TokensEx.CreateDefaultAdministratorTokenPrivileges(); var tokenGroups = (Win32.Tokens.TOKEN_GROUPS)Marshal.PtrToStructure( pTokenGroups, typeof(Win32.Tokens.TOKEN_GROUPS)); var tokenOwner = (Win32.Tokens.TOKEN_OWNER)Marshal.PtrToStructure(pTokenOwner, typeof(Win32.Tokens.TOKEN_OWNER)); var tokenPrimaryGroup = (Win32.Tokens.TOKEN_PRIMARY_GROUP)Marshal.PtrToStructure(pTokenPrimaryGroup, typeof(Win32.Tokens.TOKEN_PRIMARY_GROUP)); var tokenDefaultDacl = (Win32.Tokens.TOKEN_DEFAULT_DACL)Marshal.PtrToStructure( pTokenDefaultDacl, typeof(Win32.Tokens.TOKEN_DEFAULT_DACL)); var tokenSource = (Win32.Tokens.TOKEN_SOURCE)Marshal.PtrToStructure( pTokenSource, typeof(Win32.Tokens.TOKEN_SOURCE)); /* for (var idx = 0; idx < tokenPrivileges.PrivilegeCount - 1; idx++) { if ((tokenPrivileges.Privileges[idx].Attributes & (uint)Win32.Tokens.SE_PRIVILEGE_ATTRIBUTES.SE_PRIVILEGE_ENABLED) != 0) { } if ((tokenPrivileges.Privileges[idx].Attributes & (uint)Win32.Tokens.SE_PRIVILEGE_ATTRIBUTES.SE_PRIVILEGE_ENABLED_BY_DEFAULT) != 0) { } } */ Win32.SID.SafeSIDHandle adminsSid = null; Win32.SID.SafeSIDHandle localAndAdminSid = null; Win32.SID.SafeSIDHandle trustedInstallerSid = null; Win32.SID.SafeSIDHandle ntServiceSid = null; bool adminsFound = false; bool localAndAdminFound = false; for (var idx = 0; idx < tokenGroups.GroupCount - 1; idx++) { Win32.SID.ConvertSidToStringSid(tokenGroups.Groups[idx].Sid, out string strSid); if (string.Compare(strSid, Win32.SID.DOMAIN_ALIAS_RID_ADMINS, StringComparison.OrdinalIgnoreCase) == 0) { adminsFound = true; tokenGroups.Groups[idx].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES .SE_GROUP_ENABLED_BY_DEFAULT | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_MANDATORY | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_OWNER; } else if (string.Compare(strSid, Win32.SID.DOMAIN_ALIAS_RID_LOCAL_AND_ADMIN_GROUP, StringComparison.OrdinalIgnoreCase) == 0) { localAndAdminFound = true; tokenGroups.Groups[idx].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES .SE_GROUP_ENABLED_BY_DEFAULT | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_MANDATORY; } } if (!adminsFound) { Win32.SID.ConvertStringSidToSid(Win32.SID.DOMAIN_ALIAS_RID_ADMINS, out adminsSid); tokenGroups.Groups[tokenGroups.GroupCount].Sid = adminsSid.DangerousGetHandle(); tokenGroups.Groups[tokenGroups.GroupCount].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT; tokenGroups.GroupCount++; } if (!localAndAdminFound) { Win32.SID.ConvertStringSidToSid(Win32.SID.DOMAIN_ALIAS_RID_LOCAL_AND_ADMIN_GROUP, out localAndAdminSid); tokenGroups.Groups[tokenGroups.GroupCount].Sid = localAndAdminSid.DangerousGetHandle(); tokenGroups.Groups[tokenGroups.GroupCount].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT; tokenGroups.GroupCount++; } if (trustedInstaller) { Win32.SID.ConvertStringSidToSid(Win32.SID.TRUSTED_INSTALLER_RID, out trustedInstallerSid); tokenGroups.Groups[tokenGroups.GroupCount].Sid = trustedInstallerSid.DangerousGetHandle(); tokenGroups.Groups[tokenGroups.GroupCount].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT; tokenGroups.GroupCount++; Win32.SID.ConvertStringSidToSid(Win32.SID.NT_SERVICE_SID, out ntServiceSid); tokenGroups.Groups[tokenGroups.GroupCount].Sid = ntServiceSid.DangerousGetHandle(); tokenGroups.Groups[tokenGroups.GroupCount].Attributes = (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED | (uint)Win32.Tokens.SE_GROUP_ATTRIBUTES.SE_GROUP_ENABLED_BY_DEFAULT; tokenGroups.GroupCount++; } var expirationTime = new Win32.LARGE_INTEGER() { QuadPart = -1L }; var sqos = new Win32.Tokens.SECURITY_QUALITY_OF_SERVICE( Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.SECURITY_STATIC_TRACKING, 0); var oa = new Win32.Tokens.OBJECT_ATTRIBUTES(string.Empty, 0) { }; var pSqos = Marshal.AllocHGlobal(Marshal.SizeOf(sqos)); Marshal.StructureToPtr(sqos, pSqos, true); oa.SecurityQualityOfService = pSqos; var status = trustedInstaller ? Win32.Tokens.ZwCreateToken(out trustedInstallerUserToken, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, ref oa, Win32.Tokens.TOKEN_TYPE.TokenPrimary, ref authId, ref expirationTime, ref tokenUser, ref tokenGroups, ref tokenPrivileges, ref tokenOwner, ref tokenPrimaryGroup, ref tokenDefaultDacl, ref tokenSource) : Win32.Tokens.ZwCreateToken(out elevatedUserToken, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, ref oa, Win32.Tokens.TOKEN_TYPE.TokenPrimary, ref authId, ref expirationTime, ref tokenUser, ref tokenGroups, ref tokenPrivileges, ref tokenOwner, ref tokenPrimaryGroup, ref tokenDefaultDacl, ref tokenSource); Win32.LocalFree(pTokenUser); Win32.LocalFree(pTokenOwner); Win32.LocalFree(pTokenGroups); Win32.LocalFree(pTokenDefaultDacl); Win32.LocalFree(pTokenPrivileges); Win32.LocalFree(pTokenPrimaryGroup); adminsSid?.Dispose(); localAndAdminSid?.Dispose(); trustedInstallerSid?.Dispose(); ntServiceSid?.Dispose(); if (status != 0) throw new Win32Exception($"Error creating {(trustedInstaller ? "trusted installer" : "elevated")} user token: " + status); Win32.Tokens.GetTokenInformation(dupedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSessionId, out int sessionId, sizeof(int), out _); if (!Win32.Tokens.SetTokenInformation(trustedInstaller ? trustedInstallerUserToken : elevatedUserToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, sizeof(int))) { if (trustedInstaller) { trustedInstallerUserToken.Dispose(); trustedInstallerUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); } else { elevatedUserToken.Dispose(); elevatedUserToken = new Win32.TokensEx.SafeTokenHandle(IntPtr.Zero); } throw new Win32Exception("Error setting token session id: " + Marshal.GetLastWin32Error()); } } public static void GetSystemToken() { if (!systemToken.IsInvalid) return; try { using var processHandle = Win32.Process.OpenProcess(Win32.Process.ProcessAccessFlags.QueryLimitedInformation, false, Process.GetProcessesByName("winlogon").First().Id); if (!Win32.Tokens.OpenProcessToken(processHandle, Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE | Win32.Tokens.TokenAccessFlags.TOKEN_ASSIGN_PRIMARY | Win32.Tokens.TokenAccessFlags.TOKEN_QUERY | Win32.Tokens.TokenAccessFlags.TOKEN_IMPERSONATE, out var tokenHandle)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token for winlogon."); } if (!Win32.Tokens.DuplicateTokenEx(tokenHandle, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.TOKEN_TYPE.TokenPrimary, out systemToken)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate process token for winlogon."); } using var currentProcess = Process.GetCurrentProcess(); Win32.Tokens.OpenProcessToken(new SafeProcessHandle(currentProcess.Handle, false), Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, out Win32.TokensEx.SafeTokenHandle currentHandle); Win32.Tokens.GetTokenInformation(currentHandle, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSessionId, out int sessionId, sizeof(int), out _); if (!Win32.Tokens.SetTokenInformation(systemToken, Win32.Tokens.TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, sizeof(int))) { systemToken = null; throw new Win32Exception("Error setting token session id: " + Marshal.GetLastWin32Error()); } } catch (Exception e) { var sessionId = GetUserSession(); int dwLsassPID = -1; int dwWinLogonPID = -1; Win32.WTS.WTS_PROCESS_INFO[] pProcesses; IntPtr pProcessInfo = IntPtr.Zero; int dwProcessCount = 0; if (Win32.WTS.WTSEnumerateProcesses((IntPtr)null, 0, 1, ref pProcessInfo, ref dwProcessCount)) { IntPtr pMemory = pProcessInfo; pProcesses = new Win32.WTS.WTS_PROCESS_INFO[dwProcessCount]; for (int i = 0; i < dwProcessCount; i++) { pProcesses[i] = (Win32.WTS.WTS_PROCESS_INFO)Marshal.PtrToStructure(pProcessInfo, typeof(Win32.WTS.WTS_PROCESS_INFO)); pProcessInfo = (IntPtr)((long)pProcessInfo + Marshal.SizeOf(pProcesses[i])); var processName = Marshal.PtrToStringAnsi(pProcesses[i].ProcessName); Win32.SID.ConvertSidToStringSid(pProcesses[i].UserSid, out string sid); string strSid; if (processName == null || pProcesses[i].UserSid == default || sid != "S-1-5-18") continue; if ((-1 == dwLsassPID) && (0 == pProcesses[i].SessionID) && (processName == "lsass.exe")) { dwLsassPID = pProcesses[i].ProcessID; continue; } if ((-1 == dwWinLogonPID) && (sessionId == pProcesses[i].SessionID) && (processName == "winlogon.exe")) { dwWinLogonPID = pProcesses[i].ProcessID; continue; } } Win32.WTS.WTSFreeMemory(pMemory); } SafeProcessHandle systemProcessHandle; try { systemProcessHandle = new SafeProcessHandle(Process.GetProcessById(dwLsassPID).Handle, false); } catch { systemProcessHandle = new SafeProcessHandle(Process.GetProcessById(dwWinLogonPID).Handle, false); } if (!Win32.Tokens.OpenProcessToken(systemProcessHandle, Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE, out Win32.TokensEx.SafeTokenHandle token)) { systemProcessHandle.Dispose(); throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token."); } if (!Win32.Tokens.DuplicateTokenEx(token, Win32.Tokens.TokenAccessFlags.MAXIMUM_ALLOWED, IntPtr.Zero, Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Win32.Tokens.TOKEN_TYPE.TokenPrimary, out systemToken)) { systemProcessHandle.Dispose(); throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate process token."); } } } public static Win32.TokensEx.SafeTokenHandle GetCurrentProcessToken() { if (!Win32.Tokens.OpenProcessToken(Win32.Process.GetCurrentProcess(), Win32.Tokens.TokenAccessFlags.TOKEN_READ, out Win32.TokensEx.SafeTokenHandle token)) throw new Win32Exception(Marshal.GetLastWin32Error(), "Error opening token for current process."); return token; } private static Win32.TokensEx.SafeTokenHandle GetProcessTokenByName(string name, bool impersonation) { var processHandle = new SafeProcessHandle(Process.GetProcessesByName(name).First().Handle, false); if (!Win32.Tokens.OpenProcessToken(processHandle, Win32.Tokens.TokenAccessFlags.TOKEN_DUPLICATE | Win32.Tokens.TokenAccessFlags.TOKEN_ASSIGN_PRIMARY | Win32.Tokens.TokenAccessFlags.TOKEN_QUERY | Win32.Tokens.TokenAccessFlags.TOKEN_IMPERSONATE, out var tokenHandle)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to open process token for " + name + "."); } if (!Win32.Tokens.DuplicateTokenEx(tokenHandle, Win32.Tokens.TokenAccessFlags.TOKEN_ALL_ACCESS, IntPtr.Zero, impersonation ? Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation : Win32.Tokens.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, impersonation ? Win32.Tokens.TOKEN_TYPE.TokenImpersonation : Win32.Tokens.TOKEN_TYPE.TokenPrimary, out Win32.TokensEx.SafeTokenHandle handle)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate process token for " + name + "."); } return handle; } } } ================================================ FILE: TrustedUninstaller.Shared/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using TrustedUninstaller.Shared; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("TrustedUninstaller.Shared")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Ameliorated LLC")] [assembly: AssemblyProduct("TrustedUninstaller.Shared")] [assembly: AssemblyCopyright("MIT License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("9bda9d32-e9a1-4db8-9d90-443792107e28")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion(Globals.CurrentVersion)] [assembly: AssemblyFileVersion(Globals.CurrentVersion)] ================================================ FILE: TrustedUninstaller.Shared/ProviderStatus.cs ================================================ using System; using System.Runtime.InteropServices; namespace TrustedUninstaller.Shared { //Byte 0 [Flags] public enum SignatureStatusFlags : byte { UpToDate = 0, OutOfDate = 16 } // Byte 1 [Flags] public enum AVStatusFlags : byte { Unknown = 1, Enabled = 16 } // Byte 2 [Flags] public enum ProviderFlags : byte { FIREWALL = 1, AUTOUPDATE_SETTINGS = 2, ANTIVIRUS = 4, ANTISPYWARE = 8, INTERNET_SETTINGS = 16, USER_ACCOUNT_CONTROL = 32, SERVICE = 64, NONE = 0, } [StructLayout(LayoutKind.Sequential)] public struct ProviderStatus { public SignatureStatusFlags SignatureStatus; public AVStatusFlags AVStatus; public ProviderFlags SecurityProvider; public bool FileExists; public string DisplayName; } } ================================================ FILE: TrustedUninstaller.Shared/Requirements.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Management; using System.Net; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Xml.Serialization; using Core; using Interprocess; using Microsoft.Win32; using TrustedUninstaller.Shared; using TrustedUninstaller.Shared.Actions; using TrustedUninstaller.Shared.Tasks; using WUApiLib; namespace TrustedUninstaller.Shared { public static class Requirements { [Serializable] public enum Requirement { [XmlEnum("Internet")] Internet = 0, [XmlEnum("NoInternet")] NoInternet = 1, [XmlEnum("DefenderDisabled")] DefenderDisabled = 2, [XmlEnum("DefenderToggled")] DefenderToggled = 3, [XmlEnum("NoPendingUpdates")] NoPendingUpdates = 4, [XmlEnum("Activation")] Activation = 5, [XmlEnum("NoAntivirus")] NoAntivirus = 6, [XmlEnum("LocalAccounts")] LocalAccounts = 11, [XmlEnum("PasswordSet")] PasswordSet = 11, [XmlEnum("AdministratorPasswordSet")] AdministratorPasswordSet = 8, [XmlEnum("PluggedIn")] PluggedIn = 9, [XmlEnum("NoTweakware")] NoTweakware = 10, [XmlEnum("FreshInstall")] FreshInstall = 12, [XmlEnum("UCPDDisabled")] UCPDDisabled = 13, } public static async Task MetRequirements(this Requirement[] requirements, bool checkNoPendingUpdate = false) { var requirementEnum = (Requirement[])Enum.GetValues(typeof(Requirement)); if (requirements == null) { return requirementEnum; } // Add all requirements that are not included var metRequirements = requirementEnum.Except(requirements).ToList(); if (requirements.Contains(Requirement.Internet)) if (await new Internet().IsMet()) metRequirements.Add(Requirement.Internet); else metRequirements.Add(Requirement.NoInternet); if (requirements.Contains(Requirement.NoAntivirus)) if (true) metRequirements.Add(Requirement.NoAntivirus); // Handled upstream if (requirements.Contains(Requirement.Activation)) if (true) metRequirements.Add(Requirement.Activation); if (requirements.Contains(Requirement.DefenderDisabled)) if (await new DefenderDisabled().IsMet()) metRequirements.Add(Requirement.DefenderDisabled); if (requirements.Contains(Requirement.PluggedIn)) if (await new Battery().IsMet()) metRequirements.Add(Requirement.PluggedIn); if (requirements.Contains(Requirement.FreshInstall)) if (await new FreshInstall().IsMet()) metRequirements.Add(Requirement.FreshInstall); if (requirements.Contains(Requirement.NoPendingUpdates)) if (!checkNoPendingUpdate || (new [] { Requirement.FreshInstall, Requirement.PluggedIn }.All(metRequirements.Contains) && await new NoPendingUpdates().IsMet())) metRequirements.Add(Requirement.NoPendingUpdates); if (requirements.Contains(Requirement.UCPDDisabled)) if (await new UCPDDisabled().IsMet()) metRequirements.Add(Requirement.UCPDDisabled); if (requirements.Contains(Requirement.DefenderToggled)) if (await new DefenderToggled().IsMet()) metRequirements.Add(Requirement.DefenderToggled); if (requirements.Contains(Requirement.LocalAccounts)) metRequirements.Add(Requirement.LocalAccounts); if (requirements.Contains(Requirement.AdministratorPasswordSet)) metRequirements.Add(Requirement.AdministratorPasswordSet); return metRequirements.ToArray(); } public interface IRequirements { Task IsMet(); Task Meet(); } public class RequirementBase { public class ProgressEventArgs : EventArgs { public int PercentAdded; public ProgressEventArgs(int percent) { PercentAdded = percent; } } public event EventHandler ProgressChanged; protected void OnProgressAdded(int percent) { ProgressChanged?.Invoke(this, new ProgressEventArgs(percent)); } } public class Battery : RequirementBase, IRequirements { [StructLayout(LayoutKind.Sequential)] public class PowerState { public ACLineStatus ACLineStatus; public BatteryFlag BatteryFlag; public Byte BatteryLifePercent; public Byte Reserved1; public Int32 BatteryLifeTime; public Int32 BatteryFullLifeTime; // direct instantation not intended, use GetPowerState. private PowerState() {} public static PowerState GetPowerState() { PowerState state = new PowerState(); if (GetSystemPowerStatusRef(state)) return state; throw new ApplicationException("Unable to get power state"); } [DllImport("Kernel32", EntryPoint = "GetSystemPowerStatus")] private static extern bool GetSystemPowerStatusRef(PowerState sps); } // Note: Underlying type of byte to match Win32 header public enum ACLineStatus : byte { Offline = 0, Online = 1, Unknown = 255 } public enum BatteryFlag : byte { High = 1, Low = 2, Critical = 4, Charging = 8, NoSystemBattery = 128, Unknown = 255 } public async Task IsMet() { try { PowerState state = PowerState.GetPowerState(); if ((state.BatteryFlag == BatteryFlag.NoSystemBattery || state.BatteryFlag == BatteryFlag.Charging) || state.ACLineStatus == ACLineStatus.Online || (state.ACLineStatus == ACLineStatus.Unknown && state.BatteryFlag == BatteryFlag.Unknown)) return true; else return false; } catch { } return true; } public Task Meet() => throw new NotImplementedException(); } public class Internet : RequirementBase, IRequirements { [DllImport("wininet.dll", SetLastError = true)] private static extern bool InternetCheckConnection(string lpszUrl, int dwFlags, int dwReserved); [DllImport("wininet.dll", SetLastError=true)] extern static bool InternetGetConnectedState(out int lpdwFlags, int dwReserved); public async Task IsMet() { try { try { if (!InternetCheckConnection("http://archlinux.org", 1, 0)) { if (!InternetCheckConnection("http://google.com", 1, 0)) return false; } return true; } catch { var request = (HttpWebRequest)WebRequest.Create("http://google.com"); request.KeepAlive = false; request.Timeout = 5000; using (var response = (HttpWebResponse)request.GetResponse()) return true; } } catch { return false; } } public Task Meet() => throw new NotImplementedException(); } public class DefenderDisabled : RequirementBase, IRequirements { public async Task IsMet() { return !Process.GetProcessesByName("MsMpEng").Any() && !Process.GetProcessesByName("SecurityHealthService").Any() && !Process.GetProcessesByName("MpDefenderCoreService").Any(); } public async Task Meet() => throw new NotImplementedException(); } public class DefenderToggled : RequirementBase, IRequirements { public async Task IsMet() { var defenderKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows Defender"); RegistryKey realtimeKey = null; try { realtimeKey = defenderKey.OpenSubKey("Real-Time Protection"); } catch { } if (realtimeKey != null) { try { if (!((int)realtimeKey.GetValue("DisableRealtimeMonitoring") != 1)) return false; } catch (Exception exception) { return false; } } try { if (!((int)defenderKey.OpenSubKey("SpyNet").GetValue("SpyNetReporting") != 0)) return false; } catch { } try { if (!((int)defenderKey.OpenSubKey("SpyNet").GetValue("SubmitSamplesConsent") != 0)) return false; } catch { } try { if (!((int)defenderKey.OpenSubKey("Features").GetValue("TamperProtection") != 4)) return false; } catch { } return true; } public async Task Meet() { throw new NotImplementedException(); } } class SearchCompletedCallback : ISearchCompletedCallback { public void Invoke(ISearchJob searchJob, ISearchCompletedCallbackArgs callbackArgs) { this.CompleteTask(); } private TaskCompletionSource taskSource = new TaskCompletionSource(); protected void CompleteTask() { taskSource.SetResult(true); } public Task Task { get { return taskSource.Task; } } } public class NoPendingUpdates : RequirementBase, IRequirements { public async Task IsMet() { // Using WUApiLib can crash the entire application if // Windows Update is faulty. For that reason we use a // separate process. To replicate, use an ameliorated // system and copy wuapi.dll & wuaeng.dll to System32. var result = await InterLink.ExecuteDisposableSafeAsync(TargetLevel.User, () => CheckDisposable(), logExceptions: true); if (result.Failed) return true; return result.Value; } public Task Meet() => throw new NotImplementedException(); [InterprocessMethod(Level.User)] private static bool CheckDisposable() { try { var updateSession = new UpdateSession(); var updateSearcher = updateSession.CreateUpdateSearcher(); updateSearcher.Online = false; //set to true if you want to search online SearchCompletedCallback searchCompletedCallback = new SearchCompletedCallback(); ISearchJob searchJob = updateSearcher.BeginSearch("IsInstalled=0 And IsHidden=0 And Type='Software' And DeploymentAction=*", searchCompletedCallback, null); try { searchCompletedCallback.Task.Wait(50000); } catch (OperationCanceledException) { searchJob.RequestAbort(); } ISearchResult searchResult = updateSearcher.EndSearch(searchJob); if (searchResult.Updates.Cast().Any(x => x.IsDownloaded)) { return false; } } catch (Exception e) { Log.EnqueueExceptionSafe(e); return true; } return true; } } public class NoAntivirus : RequirementBase, IRequirements { public async Task IsMet() { return !WinUtil.GetEnabledAvList(false).Any(); } public Task Meet() => throw new NotImplementedException(); } public class Activation : RequirementBase, IRequirements { public async Task IsMet() { return WinUtil.IsGenuineWindows(); } public Task Meet() => throw new NotImplementedException(); } public class FreshInstall : RequirementBase, IRequirements { public async Task IsMet() { try { RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); if (key != null) { object installDateValue = key.GetValue("InstallDate"); if (installDateValue != null) { int installDateUnix = (int)installDateValue; DateTime installDate = DateTimeOffset.FromUnixTimeSeconds(installDateUnix).DateTime; TimeSpan timeSinceInstall = DateTime.Now - installDate; if (timeSinceInstall.TotalHours > 40) return false; } } } catch (Exception e) { Log.EnqueueExceptionSafe(e); } return true; } public async Task Meet() => throw new NotImplementedException(); } public class UCPDDisabled : RequirementBase, IRequirements { public async Task IsMet() { try { RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Services\UCPD"); if (key != null) { int startValue= (int)key.GetValue("Start"); if (startValue != 4) return false; } } catch (Exception e) { Log.EnqueueExceptionSafe(e); } return true; } public async Task Meet() => throw new NotImplementedException(); } public class WindowsBuild { public bool IsMet(string[] builds) { return builds.Any(x => x.Equals(Win32.SystemInfoEx.WindowsVersion.BuildNumber.ToString())); } public Task Meet() => throw new NotImplementedException(); } } } ================================================ FILE: TrustedUninstaller.Shared/Tasks/ITaskAction.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Core; namespace TrustedUninstaller.Shared.Tasks { public enum Scope { AllUsers = 0, CurrentUser = 1, ActiveUsers = 2, DefaultUser = 3 } public interface ITaskAction { public ErrorAction GetDefaultErrorAction(); public bool GetRetryAllowed(); public int GetProgressWeight(); public void ResetProgress(); public string ErrorString(); public UninstallTaskStatus GetStatus(Output.OutputWriter output); public Task RunTask(Output.OutputWriter output); public void RunTaskOnMainThread(Output.OutputWriter output); } } ================================================ FILE: TrustedUninstaller.Shared/Tasks/OutputProcessor.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Core; using JetBrains.Annotations; using TrustedUninstaller.Shared.Actions; using static Core.Output; namespace TrustedUninstaller.Shared.Tasks { public abstract class TaskActionWithOutputProcessor : Tasks.TaskAction { protected static bool ExeRunning(string name, int id) { try { return Process.GetProcessesByName(Path.GetFileNameWithoutExtension(name)).Any(x => x.Id == id); } catch (Exception) { return false; } } protected class OutputHandler : IDisposable { [System.Runtime.InteropServices.DllImport("kernel32.dll")] static extern uint GetOEMCP(); private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); private readonly OutputWriter writer; private readonly string name; private readonly Process process; private readonly AugmentedProcess.Process augmentedProcess; private static readonly Encoding s_outputEncoding = Encoding.GetEncoding((int)GetOEMCP()); public OutputHandler(string name, Process process, [NotNull] OutputWriter writer) { this.name = name; this.writer = writer; this.process = process; if (!process.StartInfo.RedirectStandardOutput) OutputEndReached.Set(); if (!process.StartInfo.RedirectStandardError) ErrorEndReached.Set(); if (process.StartInfo.RedirectStandardOutput) { process.StartInfo.StandardOutputEncoding = s_outputEncoding; process.OutputDataReceived += WriteOutputSafe; } if (process.StartInfo.RedirectStandardError) { process.StartInfo.StandardErrorEncoding = s_outputEncoding; process.ErrorDataReceived += WriteErrorSafe; } } public OutputHandler(string name, AugmentedProcess.Process process, [NotNull] OutputWriter writer) { this.name = name; this.writer = writer; this.augmentedProcess = process; if (!process.StartInfo.RedirectStandardOutput) OutputEndReached.Set(); if (!process.StartInfo.RedirectStandardError) ErrorEndReached.Set(); if (process.StartInfo.RedirectStandardOutput) { process.StartInfo.StandardOutputEncoding = s_outputEncoding; process.OutputDataReceived += WriteOutputSafe; } if (process.StartInfo.RedirectStandardError) { process.StartInfo.StandardErrorEncoding = s_outputEncoding; process.ErrorDataReceived += WriteErrorSafe; } } public void StartProcess(Privilege privilege = Privilege.TrustedInstaller) { if (process != null) { process.Start(); if (process.StartInfo.RedirectStandardOutput) process.BeginOutputReadLine(); if (process.StartInfo.RedirectStandardError) process.BeginErrorReadLine(); } else if (augmentedProcess != null) { if (privilege == Privilege.TrustedInstaller) { var currentToken = Win32.TokensEx.GetCurrentProcessToken(); augmentedProcess.Start(AugmentedProcess.Process.CreateType.UserToken, ref currentToken); currentToken.Dispose(); } else ProcessPrivilege.StartPrivilegedTask(augmentedProcess, privilege); if (augmentedProcess.StartInfo.RedirectStandardOutput) augmentedProcess.BeginOutputReadLine(); if (augmentedProcess.StartInfo.RedirectStandardError) augmentedProcess.BeginErrorReadLine(); } } public void CancelReading() { if (process != null) { if (process.StartInfo.RedirectStandardOutput) { process.CancelOutputRead(); using (TryLock(5000)) { outputTask = outputTask.ContinueWith(_ => { if (lastThree.Count > 0 && lastThree.Last().Pattern.IsNullOrEmpty) lastThree.Last().Stream?.Erase(); foreach (var group in progressGroups) group.Lines.ForEach(x => x.Stream?.Dispose()); lastThree.ForEach(x => x.Stream?.Dispose()); progressGroups.Clear(); lastThree.Clear(); OutputEndReached.Set(); }); } } if (process.StartInfo.RedirectStandardError) { process.CancelErrorRead(); using (TryLock(5000)) { outputTask = outputTask.ContinueWith(_ => { if (lastThree.Count > 0 && lastThree.Last().Pattern.IsNullOrEmpty) lastThree.Last().Stream?.Erase(); foreach (var group in progressGroups) group.Lines.ForEach(x => x.Stream?.Dispose()); lastThree.ForEach(x => x.Stream?.Dispose()); progressGroups.Clear(); lastThree.Clear(); ErrorEndReached.Set(); }); } } } else if (augmentedProcess != null) { if (augmentedProcess.StartInfo.RedirectStandardOutput) { augmentedProcess.CancelOutputRead(); using (TryLock(5000)) { outputTask = outputTask.ContinueWith(_ => { if (lastThree.Count > 0 && lastThree.Last().Pattern.IsNullOrEmpty) lastThree.Last().Stream?.Erase(); foreach (var group in progressGroups) group.Lines.ForEach(x => x.Stream?.Dispose()); lastThree.ForEach(x => x.Stream?.Dispose()); progressGroups.Clear(); lastThree.Clear(); OutputEndReached.Set(); }); } } if (augmentedProcess.StartInfo.RedirectStandardError) { augmentedProcess.CancelErrorRead(); using (TryLock(5000)) { outputTask = outputTask.ContinueWith(_ => { if (lastThree.Count > 0 && lastThree.Last().Pattern.IsNullOrEmpty) lastThree.Last().Stream?.Erase(); foreach (var group in progressGroups) group.Lines.ForEach(x => x.Stream?.Dispose()); lastThree.ForEach(x => x.Stream?.Dispose()); progressGroups.Clear(); lastThree.Clear(); ErrorEndReached.Set(); }); } } } if (!OutputEndReached.Wait(2500)) Log.EnqueueSafe(LogType.Warning, "Output processing did not finish after cancel.", new SerializableTrace(), writer.LogOptions); if (!ErrorEndReached.Wait(2500)) Log.EnqueueSafe(LogType.Warning, "Error processing did not finish after cancel.", new SerializableTrace(), writer.LogOptions); } private IDisposable TryLock(int timout = Timeout.Infinite) { if (!semaphore.Wait(timout)) return null; return new Releaser(semaphore); } public void Dispose() { bool dispose = true; if (!OutputEndReached.Wait(5000)) { dispose = false; Log.EnqueueSafe(LogType.Warning, "Output processing did not finish.", new SerializableTrace(), writer.LogOptions); } if (!ErrorEndReached.Wait(5000)) { dispose = false; Log.EnqueueSafe(LogType.Warning, "Error processing did not finish.", new SerializableTrace(), writer.LogOptions); } if (dispose) { semaphore.Dispose(); } } private class Releaser : IDisposable { private readonly SemaphoreSlim semaphore; public Releaser(SemaphoreSlim semaphore) => this.semaphore = semaphore; public void Dispose() { try { semaphore.Release(); } catch (ObjectDisposedException) { } } } private readonly ManualResetEventSlim OutputEndReached = new ManualResetEventSlim(false); private readonly ManualResetEventSlim ErrorEndReached = new ManualResetEventSlim(false); private readonly List lastThree = new List(); private readonly List progressGroups = new List(); private Task outputTask = Task.CompletedTask; private void WriteErrorSafe(object _, object args) { var text = args is DataReceivedEventArgs ? ((DataReceivedEventArgs)args).Data : ((AugmentedProcess.DataReceivedEventArgs)args).Data; if (text == null) { if (Monitor.TryEnter(this)) { try { OutputEndReached.Wait(5000); } finally { Monitor.Exit(this); } } using (TryLock(5000)) { outputTask = outputTask.ContinueWith(_ => { if (lastThree.Count > 0 && lastThree.Last().Pattern.IsNullOrEmpty) lastThree.Last().Stream?.Erase(); foreach (var group in progressGroups) group.Lines.ForEach(x => x.Stream?.Dispose()); lastThree.ForEach(x => x.Stream?.Dispose()); progressGroups.Clear(); lastThree.Clear(); ErrorEndReached.Set(); }); } return; } using (TryLock(5000)) outputTask = outputTask.ContinueWith(_ => Wrap.ExecuteSafe(() => WriteError(text), true)); } private void WriteError([NotNull] string text) { if (ErrorEndReached.Wait(0)) { if (!string.IsNullOrWhiteSpace(text)) Log.EnqueueSafe(LogType.Warning, $"Unexpected error output received after null:\r\n{text}", new SerializableTrace(), writer.LogOptions); return; } WriteUnsafe(name + " | Err", text); } private void WriteOutputSafe(object _, object args) { var text = args is DataReceivedEventArgs ? ((DataReceivedEventArgs)args).Data : ((AugmentedProcess.DataReceivedEventArgs)args).Data; if (text == null) { if (Monitor.TryEnter(this)) { try { ErrorEndReached.Wait(5000); } finally { Monitor.Exit(this); } } using (TryLock(5000)) { outputTask = outputTask.ContinueWith(_ => { if (lastThree.Count > 0 && lastThree.Last().Pattern.IsNullOrEmpty) lastThree.Last().Stream?.Erase(); foreach (var group in progressGroups) group.Lines.ForEach(x => x.Stream?.Dispose()); lastThree.ForEach(x => x.Stream?.Dispose()); progressGroups.Clear(); lastThree.Clear(); OutputEndReached.Set(); }); } return; } using (TryLock(5000)) outputTask = outputTask.ContinueWith(_ => Wrap.ExecuteSafe(() => WriteOutput(text), true)); } private void WriteOutput([NotNull] string text) { if (OutputEndReached.Wait(0)) { if (!string.IsNullOrWhiteSpace(text)) Log.EnqueueSafe(LogType.Warning, $"Unexpected output received after null:\r\n{text}", new SerializableTrace(), writer.LogOptions); return; } WriteUnsafe(name + " | Out", text); } private void WriteUnsafe(string type, [NotNull] string text) { int index = text.IndexOf('>'); if (index >= 0) { string directory = text.Substring(0, index); if (Directory.Exists(directory)) text = text.Substring(index + 1).Trim(); } if (text.Length > 512) { AddToCycle(null, new ProgressPattern(null)); if (progressGroups.Count > 0) { foreach (var group in new List(progressGroups)) { if (group.Cycles >= group.Lines.Count - 1) { progressGroups.Remove(group); group.Lines.ForEach(x => { if (!progressGroups.Any(y => y.Lines.Contains(x)) && !lastThree.Contains(x)) { x.Stream?.Dispose(); x.Stream = null; } }); continue; } group.Cycles++; } } writer.WriteLineSafe(type, text); return; } var pattern = new ProgressPattern(text); AddToCycle(null, pattern); ProgressGroup handledGroup = null; ProgressGroup addedGroup = null; if (!string.IsNullOrWhiteSpace(text) && pattern.HasProgressElements) { for (int j = 0; j < 3; j++) { if (j > 0 && (lastThree.Count < j || !lastThree[j - 1].IsProgress)) continue; foreach (var group in new List(progressGroups)) { if (group.Cycles == 2 - j && ProgressPattern.SequenceEqual(lastThree, j, group.Lines, j)) { bool nullStream = false; for (var i = j; i < lastThree.Count; i++) { if (i < j) continue; if (group.Lines[i].Stream == null) { nullStream = true; break; } if (group.Lines[i].Stream.Flushed) { group.Lines[i].Pattern = lastThree[i].Pattern; group.Lines[i].Stream.WriteSafe(lastThree[i].Pattern.GetString()); } else { group.Lines[i].Stream.WriteSafe(lastThree[i].Pattern.GetString(group.Lines[i].Pattern)); } lastThree[i].IsProgress = group.Lines[i].IsProgress; if (!ReferenceEquals(lastThree[i].Pattern, pattern) && !group.Lines.Any(x => x == lastThree[i])) { progressGroups.RemoveAll(x => x.Lines.Any(y => y == lastThree[i])); lastThree[i].Stream?.Erase(); lastThree[i].Stream?.Dispose(); lastThree[i].Stream = null; group.Cycles--; } } if (nullStream) continue; handledGroup = group; break; } } } if (handledGroup == null) { lastThree.Last().IsProgress = true; progressGroups.Add(new ProgressGroup(lastThree)); addedGroup = progressGroups.Last(); } } if (progressGroups.Count > 0) { foreach (var group in new List(progressGroups.Where(x => x != handledGroup && x != addedGroup))) { if (group.Cycles >= group.Lines.Count - 1 || handledGroup != null) { progressGroups.Remove(group); group.Lines.ForEach(x => { if (!progressGroups.Any(y => y.Lines.Contains(x)) && !lastThree.Contains(x)) { x.Stream?.Dispose(); x.Stream = null; } }); continue; } group.Cycles++; } } if (handledGroup != null) return; lastThree.Last().Stream = new OutputWriter.LineStream(writer, type); lastThree.Last().Stream.WriteSafe(text); } private void AddToCycle(OutputWriter.LineStream stream, ProgressPattern pattern) { if (lastThree.Count == 3) { if (lastThree[0].Stream != null && !progressGroups.Any(x => x.Lines.Any(y => y.Stream == lastThree[0].Stream))) { lastThree[0].Stream.Dispose(); lastThree[0].Stream = null; } lastThree.RemoveAt(0); } lastThree.Add(new ProgressLine(stream, pattern)); while (lastThree.Count < 3) { lastThree.Insert(0, new ProgressLine(null, new ProgressPattern(string.Empty))); } } } private class ProgressLine { public OutputWriter.LineStream Stream; public ProgressPattern Pattern; public bool IsProgress = false; public ProgressLine(OutputWriter.LineStream stream, ProgressPattern pattern) => (Stream, Pattern) = (stream, pattern); } private class ProgressGroup { public readonly List Lines; public int Cycles; public ProgressGroup(List lines) => Lines = new List(lines); } private class ProgressPattern { private ProgressPattern() { } private static readonly string[] DefiniteProceedingIndicators = { "%", }; // Must be sorted by length private static readonly string[] ProceedingIndicators = { "KiB/s", "MiB/s", "GiB/s", "TiB/s", ")...", "kB/s", "mB/s", "gB/s", "tB/s", "mins", "secs", "KiBs", "MiBs", "GiBs", "TiBs", "KB/s", "MB/s", "GB/s", "TB/s", "B/s", "KiB", "MiB", "GiB", "TiB", "min", "sec", "...", "KBs", "MBs", "GBs", "TBs", "KB", "MB", "GB", "TB", "ms", "m", "s", "|", "%", }; private static readonly string[] DefinitePreceedingIndicators = { }; // Must be sorted by length private static readonly string[] PreceedingIndicators = { "|", }; private static readonly char[] ParsableChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', }; private static readonly char[] ProgressChars = { '>', '#', '=', '*', '.', '\u2593', '\u25a0', '\u25a0', '\u258f', '\u258e', '\u258d', '\u258c', '\u258b', '\u258a', '\u2589', '\u2588' }; private static readonly char[] ReplaceableChars = { '-', ' ', '\u2592', '\u25a2', '\u25a1' }; private static readonly char[] PreceedingWrapChars = { ' ', '\n', '\r', '(', '[', ':' }; private static readonly char[] ProceedingWrapChars = { ' ', '\n', '\r', ')', ']', ':' }; private class ProgressItem { public bool IsParsable { get; private set; } public string Text { get; private set; } public int Index { get; set; } [CanBeNull] public string ProceedingIndicator { get; private set; } public ProgressItem(bool isParsable, string text, int index, [CanBeNull] string proceedingIndicator) => (IsParsable, Text, Index, ProceedingIndicator) = (isParsable, text, index, proceedingIndicator); public static bool SequenceEqual([NotNull] List first, [NotNull] List second) { if (first.Count != second.Count) return false; for (var i = 0; i < first.Count; i++) { if (first[i].IsParsable != second[i].IsParsable) return false; if (first[i].Index != second[i].Index) return false; } return true; } } private static readonly char[] OutOfSeparators = { '/', '-' }; private List ProgressExcludedChars { get; set; } = new List(); private List ProgressItems { get; set; } = new List(); public bool HasProgressElements => ProgressItems.Count > 0; public readonly bool IsNullOrEmpty = false; public ProgressPattern(string text) { if (string.IsNullOrEmpty(text)) { IsNullOrEmpty = true; return; } int i = 0; while (i < text.Length) { var startItemCount = ProgressItems.Count; if (ParsableChars.Contains(text[i])) { int parseIndex = i; bool separatorFound = false; while (parseIndex < text.Length && (ParsableChars.Contains(text[parseIndex]) || (text.Length > parseIndex + 1 && ParsableChars.Contains(text[parseIndex + 1]) && (!separatorFound && (text[parseIndex] == '.' || text[parseIndex] == ','))))) { if (text[parseIndex] == '.' || text[parseIndex] == ',') separatorFound = true; parseIndex++; } var proceeding = text.Substring(parseIndex, text.Length - parseIndex); var preceeding = text.Substring(0, i); string proceedingIndicator = null; // Account for expressions like 100MB/200MB or 100/200 int outOfIndex = 0; if (text.Length > parseIndex + 1 && (OutOfSeparators.Contains(text[parseIndex]) || ((proceedingIndicator = ProceedingIndicators.FirstOrDefault(x => text.Length > parseIndex + x.Length + 1 && proceeding.StartsWith(x))) != null && OutOfSeparators.Contains(text[parseIndex + proceedingIndicator.Length])))) { outOfIndex = proceedingIndicator == null ? parseIndex + 1 : parseIndex + proceedingIndicator.Length + 1; if (ParsableChars.Contains(text[outOfIndex])) { for (; outOfIndex < text.Length && (ParsableChars.Contains(text[outOfIndex]) || (text.Length > outOfIndex + 1 && ParsableChars.Contains(text[outOfIndex + 1]) && (text[outOfIndex] == '.' || text[outOfIndex] == ','))); outOfIndex++) { } var outOfProceeding = text.Substring(outOfIndex, text.Length - outOfIndex); string outOfProceedingIndicator; if ((outOfProceedingIndicator = DefiniteProceedingIndicators.FirstOrDefault(x => outOfProceeding.StartsWith(x))) != null) ProgressItems.Add(new ProgressItem(true, text.Substring(i, parseIndex - i).Replace(',', '.'), ProgressExcludedChars.Count, proceedingIndicator)); else if (outOfProceeding.Length == 0 || ProceedingWrapChars.Contains(outOfProceeding[0]) || (outOfProceedingIndicator = ProceedingIndicators.FirstOrDefault(x => outOfProceeding.StartsWith(x) && (outOfProceeding.Length == x.Length || ProceedingWrapChars.Contains(outOfProceeding[x.Length])))) != null) ProgressItems.Add(new ProgressItem(true, text.Substring(i, parseIndex - i).Replace(',', '.'), ProgressExcludedChars.Count, proceedingIndicator)); if (outOfProceeding != null) outOfIndex += outOfProceedingIndicator?.Length ?? 0; } } else if ((proceedingIndicator = DefiniteProceedingIndicators.FirstOrDefault(x => proceeding.StartsWith(x))) != null) ProgressItems.Add(new ProgressItem(true, text.Substring(i, parseIndex - i).Replace(',', '.'), ProgressExcludedChars.Count, proceedingIndicator)); else if (DefinitePreceedingIndicators.Any(x => preceeding.EndsWith(x))) ProgressItems.Add(new ProgressItem(true, text.Substring(i, parseIndex - i).Replace(',', '.'), ProgressExcludedChars.Count, null)); else if ((proceeding.Length == 0 || ProceedingWrapChars.Contains(proceeding[0]) || (proceedingIndicator = ProceedingIndicators.FirstOrDefault(x => proceeding.StartsWith(x) && (proceeding.Length == x.Length || ProceedingWrapChars.Contains(proceeding[x.Length])))) != null) && (preceeding.Length == 0 || PreceedingWrapChars.Contains(preceeding.Last()) || PreceedingIndicators.Any(x => preceeding.EndsWith(x) && (preceeding.Length == x.Length || PreceedingWrapChars.Contains(preceeding[(preceeding.Length - 1) - x.Length]))))) ProgressItems.Add(new ProgressItem(true, text.Substring(i, parseIndex - i).Replace(',', '.'), ProgressExcludedChars.Count, proceedingIndicator)); if (startItemCount != ProgressItems.Count) { if (proceedingIndicator != null) { foreach (var proceedingChar in proceedingIndicator) { ProgressExcludedChars.Add(proceedingChar); parseIndex++; } } while (parseIndex < outOfIndex) { ProgressExcludedChars.Add(text[parseIndex]); parseIndex++; } i = parseIndex; continue; } else { if (i == parseIndex) ProgressExcludedChars.Add(text[i]); else { for (; i < parseIndex && i < text.Length; i++) { ProgressExcludedChars.Add(text[i]); } } continue; } } if (ProgressChars.Contains(text[i])) { int parseIndex = i; bool replaceableFound = false; bool continueLoop = false; while (parseIndex < text.Length) { if (ReplaceableChars.Contains(text[parseIndex])) { replaceableFound = true; parseIndex++; continue; } if (ProgressChars.Contains(text[parseIndex])) { if (replaceableFound) break; parseIndex++; continue; } if (ParsableChars.Contains(text[parseIndex])) { continueLoop = true; var internalPreceeding = text.Substring(0, i); if (!(internalPreceeding.Length == 0 || PreceedingWrapChars.Contains(internalPreceeding.Last()) || PreceedingIndicators.Any(x => internalPreceeding.EndsWith(x) && (internalPreceeding.Length == x.Length || PreceedingWrapChars.Contains(internalPreceeding[(internalPreceeding.Length - 1) - x.Length]))))) break; var progressCharEnd = parseIndex; bool separatorFound = false; while (parseIndex < text.Length && (ParsableChars.Contains(text[parseIndex]) || (text.Length > parseIndex + 1 && ParsableChars.Contains(text[parseIndex + 1]) && (!separatorFound && (text[parseIndex] == '.' || text[parseIndex] == ','))))) { if (text[parseIndex] == '.' || text[parseIndex] == ',') separatorFound = true; parseIndex++; } var parsableProceeding = text.Substring(parseIndex, text.Length - parseIndex); string parsableProceedingIndicator = null; // Account for expressions like 100MB/200MB or 100/200 int outOfIndex = 0; if (text.Length > parseIndex + 1 && (OutOfSeparators.Contains(text[parseIndex]) || ((parsableProceedingIndicator = ProceedingIndicators.FirstOrDefault(x => text.Length > parseIndex + x.Length + 1 && parsableProceeding.StartsWith(x))) != null && OutOfSeparators.Contains(text[parseIndex + parsableProceedingIndicator.Length])))) { outOfIndex = parsableProceedingIndicator == null ? parseIndex + 1 : parseIndex + parsableProceedingIndicator.Length + 1; if (ParsableChars.Contains(text[outOfIndex])) { for (; outOfIndex < text.Length && (ParsableChars.Contains(text[outOfIndex]) || (text.Length > outOfIndex + 1 && ParsableChars.Contains(text[outOfIndex + 1]) && (text[outOfIndex] == '.' || text[outOfIndex] == ','))); outOfIndex++) { } var outOfProceeding = text.Substring(outOfIndex, text.Length - outOfIndex); string outOfProceedingIndicator; if ((outOfProceedingIndicator = DefiniteProceedingIndicators.FirstOrDefault(x => outOfProceeding.StartsWith(x))) != null) ProgressItems.Add(new ProgressItem(true, text.Substring(progressCharEnd, parseIndex - progressCharEnd).Replace(',', '.'), ProgressExcludedChars.Count, parsableProceedingIndicator)); else if (outOfProceeding.Length == 0 || (ProgressChars.Contains(outOfProceeding[0]) || ReplaceableChars.Contains(outOfProceeding[0]) || ProceedingWrapChars.Contains(outOfProceeding[0])) || (outOfProceedingIndicator = ProceedingIndicators.FirstOrDefault(x => outOfProceeding.StartsWith(x) && (outOfProceeding.Length == x.Length || ProgressChars.Contains(outOfProceeding[x.Length]) || ReplaceableChars.Contains(outOfProceeding[x.Length]) || ProceedingWrapChars.Contains(outOfProceeding[x.Length])))) != null) ProgressItems.Add(new ProgressItem(true, text.Substring(progressCharEnd, parseIndex - progressCharEnd).Replace(',', '.'), ProgressExcludedChars.Count, parsableProceedingIndicator)); if (outOfProceeding != null) outOfIndex += outOfProceedingIndicator?.Length ?? 0; } } else if ((parsableProceedingIndicator = DefiniteProceedingIndicators.FirstOrDefault(x => parsableProceeding.StartsWith(x))) != null) ProgressItems.Add(new ProgressItem(true, text.Substring(progressCharEnd, parseIndex - progressCharEnd).Replace(',', '.'), ProgressExcludedChars.Count, parsableProceedingIndicator)); else if (parsableProceeding.Length == 0 || ProceedingWrapChars.Contains(parsableProceeding[0]) || (parsableProceedingIndicator = ProceedingIndicators.FirstOrDefault(x => parsableProceeding.StartsWith(x) && (parsableProceeding.Length == x.Length || ProgressChars.Contains(parsableProceeding[x.Length]) || ReplaceableChars.Contains(parsableProceeding[x.Length]) || ProceedingWrapChars.Contains(parsableProceeding[x.Length])))) != null) ProgressItems.Add(new ProgressItem(true, text.Substring(progressCharEnd, parseIndex - progressCharEnd).Replace(',', '.'), ProgressExcludedChars.Count, parsableProceedingIndicator)); if (startItemCount != ProgressItems.Count) { ProgressItems.Insert(ProgressItems.Count - 1, new ProgressItem(false, text.Substring(i, progressCharEnd - i), ProgressExcludedChars.Count, null)); startItemCount = ProgressItems.Count; if (parsableProceedingIndicator != null) { foreach (var proceedingChar in parsableProceedingIndicator) { ProgressExcludedChars.Add(proceedingChar); parseIndex++; } } while (parseIndex < outOfIndex) { ProgressExcludedChars.Add(text[parseIndex]); parseIndex++; } i = parseIndex; bool secondPartReplaceableFound = false; while (parseIndex < text.Length) { if (ReplaceableChars.Contains(text[parseIndex])) { secondPartReplaceableFound = true; parseIndex++; continue; } if (ProgressChars.Contains(text[parseIndex])) { if (secondPartReplaceableFound) break; parseIndex++; continue; } break; } var secondPartProceeding = text.Substring(parseIndex, text.Length - parseIndex); string secondPartProceedingIndicator = null; if ((secondPartProceeding.Length == 0 || ProceedingWrapChars.Contains(secondPartProceeding[0]) || text[parseIndex - 1] == ' ' || (secondPartProceedingIndicator = ProceedingIndicators.FirstOrDefault(x => secondPartProceeding.StartsWith(x) && (secondPartProceeding.Length == x.Length || ProceedingWrapChars.Contains(secondPartProceeding[x.Length])))) != null)) { var value = text.Substring(i, parseIndex - i); ProgressItems.Add(new ProgressItem(false, value, ProgressExcludedChars.Count, secondPartProceedingIndicator)); } if (startItemCount != ProgressItems.Count) { i = parseIndex; } } } break; } if (continueLoop) { if (startItemCount == ProgressItems.Count) { if (i == parseIndex) ProgressExcludedChars.Add(text[i]); else { for (; i < parseIndex && i < text.Length; i++) { ProgressExcludedChars.Add(text[i]); } } } continue; } var proceeding = text.Substring(parseIndex, text.Length - parseIndex); var preceeding = text.Substring(0, i); string proceedingIndicator = null; if ((proceeding.Length == 0 || ProceedingWrapChars.Contains(proceeding[0]) || (proceedingIndicator = ProceedingIndicators.FirstOrDefault(x => proceeding.StartsWith(x) && (proceeding.Length == x.Length || ProceedingWrapChars.Contains(proceeding[x.Length])))) != null) && (preceeding.Length == 0 || PreceedingWrapChars.Contains(preceeding.Last()) || PreceedingIndicators.Any(x => preceeding.EndsWith(x) && (preceeding.Length == x.Length || PreceedingWrapChars.Contains(preceeding[(preceeding.Length - 1) - x.Length]))))) { var value = text.Substring(i, parseIndex - i); ProgressItems.Add(new ProgressItem(false, value, ProgressExcludedChars.Count, proceedingIndicator)); } if (startItemCount != ProgressItems.Count) { if (proceedingIndicator != null) { foreach (var proceedingChar in proceedingIndicator) { ProgressExcludedChars.Add(proceedingChar); parseIndex++; } } i = parseIndex; continue; } else { if (i == parseIndex) ProgressExcludedChars.Add(text[i]); else { for (; i < parseIndex && i < text.Length; i++) { ProgressExcludedChars.Add(text[i]); } } continue; } } ProgressExcludedChars.Add(text[i]); i++; } } private readonly ConcurrentDictionary _matches = new ConcurrentDictionary(); public bool Compare([NotNull] ProgressPattern pattern) { if (_matches.TryGetValue(pattern, out _)) return true; if (pattern.IsNullOrEmpty && IsNullOrEmpty) { _matches.TryAdd(pattern, pattern); return true; } if (pattern.ProgressItems.Count(x => x.IsParsable) != ProgressItems.Count(x => x.IsParsable)) return false; if (pattern.ProgressExcludedChars.SequenceEqual(ProgressExcludedChars)) { if (ProgressItem.SequenceEqual(pattern.ProgressItems, ProgressItems)) { _matches.TryAdd(pattern, pattern); return true; } } int patternIndex = 0; int thisIndex = 0; var result = new ProgressPattern(); foreach (var patternItem in pattern.ProgressItems) { result.ProgressItems.Add(new ProgressItem(patternItem.IsParsable, patternItem.Text, patternItem.Index, patternItem.ProceedingIndicator)); } while (true) { while (patternIndex <= pattern.ProgressExcludedChars.Count && thisIndex <= this.ProgressExcludedChars.Count) { ProgressItem item; if (thisIndex != this.ProgressExcludedChars.Count && ReplaceableChars.Contains(this.ProgressExcludedChars[thisIndex]) && (item = pattern.ProgressItems.FirstOrDefault(x => x.Index == patternIndex && !x.IsParsable)) != null) { var upper = thisIndex + item.Text.Length; if (upper > this.ProgressExcludedChars.Count) return false; var originalIndex = thisIndex; while (thisIndex < this.ProgressExcludedChars.Count && ReplaceableChars.Contains(this.ProgressExcludedChars[thisIndex])) { thisIndex++; } var i = this.ProgressItems.IndexOf(item); result.ProgressItems.Insert(i, new ProgressItem(false, item.Text, item.Index, item.ProceedingIndicator)); for (i++; i < result.ProgressItems.Count; i++) result.ProgressItems[i].Index -= thisIndex - originalIndex; } else if (patternIndex != pattern.ProgressExcludedChars.Count && ReplaceableChars.Contains(pattern.ProgressExcludedChars[patternIndex]) && (item = this.ProgressItems.FirstOrDefault(x => x.Index == thisIndex && !x.IsParsable)) != null) { var upper = patternIndex + item.Text.Length; if (upper > pattern.ProgressExcludedChars.Count) return false; var originalIndex = patternIndex; while (patternIndex < pattern.ProgressExcludedChars.Count && ReplaceableChars.Contains(pattern.ProgressExcludedChars[patternIndex])) { patternIndex++; } var i = this.ProgressItems.IndexOf(item); result.ProgressItems.Insert(i, new ProgressItem(false, item.Text, item.Index, item.ProceedingIndicator)); for (i++; i < result.ProgressItems.Count; i++) result.ProgressItems[i].Index -= patternIndex - originalIndex; } if (patternIndex == pattern.ProgressExcludedChars.Count || thisIndex == this.ProgressExcludedChars.Count) break; bool matches = pattern.ProgressExcludedChars[patternIndex] == this.ProgressExcludedChars[thisIndex]; if (!matches) return false; patternIndex++; thisIndex++; } if (!ProgressItem.SequenceEqual(result.ProgressItems, this.ProgressItems)) return false; if (patternIndex == pattern.ProgressExcludedChars.Count && thisIndex == this.ProgressExcludedChars.Count) { _matches.TryAdd(pattern, result); return true; } return false; } } public static bool SequenceEqual([NotNull] List first, int skip1, [NotNull] List second, int skip2) { if (first.Count - skip1 != second.Count - skip2 || first.Count - skip1 <= 0) return false; int i1, i2; for (i1 = skip1, i2 = skip2; i1 < first.Count - skip1 && i2 < second.Count - skip2; i1++, i2++) { if (!first[i1].Compare(second[i2])) return false; } return true; } public static bool SequenceEqual([NotNull] List first, int skip1, [NotNull] List second, int skip2) { if (first.Count - skip1 != second.Count - skip2 || first.Count - skip1 <= 0) return false; int i1, i2; for (i1 = skip1, i2 = skip2; i1 < first.Count && i2 < second.Count; i1++, i2++) { if (!first[i1].Pattern.Compare(second[i2].Pattern)) return false; } return true; } public string GetString(ProgressPattern comparison) { if (!_matches.TryGetValue(comparison, out var match)) throw new InvalidOperationException("Compare must be called first."); StringBuilder builder = new StringBuilder(); int lastIndex = 0; foreach (var progressItem in ProgressItems) { for (var i = lastIndex; i < progressItem.Index && i < ProgressExcludedChars.Count; i++) { builder.Append(ProgressExcludedChars[i]); } var matchedItem = match.ProgressItems.FirstOrDefault(x => x.Index == progressItem.Index && x.IsParsable && progressItem.IsParsable); if (matchedItem != null && matchedItem.Text != progressItem.Text) builder.Append(matchedItem.Text + (matchedItem.ProceedingIndicator == "%" ? matchedItem.ProceedingIndicator : matchedItem.ProceedingIndicator == progressItem.ProceedingIndicator ? null : matchedItem.ProceedingIndicator) + " --> "); builder.Append(progressItem.Text); lastIndex = progressItem.Index; } for (var i = lastIndex; i < ProgressExcludedChars.Count; i++) { builder.Append(ProgressExcludedChars[i]); } return builder.ToString(); } public string GetString() { StringBuilder builder = new StringBuilder(); int lastIndex = 0; foreach (var progressItem in ProgressItems) { for (var i = lastIndex; i < progressItem.Index && i < ProgressExcludedChars.Count; i++) { builder.Append(ProgressExcludedChars[i]); } builder.Append(progressItem.Text); lastIndex = progressItem.Index; } for (var i = lastIndex; i < ProgressExcludedChars.Count; i++) { builder.Append(ProgressExcludedChars[i]); } return builder.ToString(); } } } } ================================================ FILE: TrustedUninstaller.Shared/Tasks/TaskAction.cs ================================================ using System.Globalization; using JetBrains.Annotations; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Tasks { public enum ErrorAction { Ignore, Log, Notify, Halt, } /// /// True: Run during ISO mastering, as well as during normal installation, but not during OOBE unless OOBE is set to True
/// Only: Only run during ISO mastering
/// False (default): Never run during ISO mastering ///
public enum ISOSetting { True, Only, False, } /// /// True: Always run during OOBE, as well as during normal installation
/// Only: Only run during OOBE
/// False: Never run during OOBE
/// Null (default): Run during normal installation, as well as OOBE unless ISO is set to True ///
public enum OOBESetting { True, Only, False, } public abstract class TaskAction { public enum ExitCodeAction { Log, Retry, Error, RetryError, Halt, } [YamlMember(typeof(ISOSetting), Alias = "iso")] public virtual ISOSetting ISO { get; set; } = ISOSetting.False; [YamlMember(typeof(OOBESetting?), Alias = "oobe")] public virtual OOBESetting? OOBE { get; set; } = null; [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 = "status")] public string Status { get; set; } = null; [YamlMember(typeof(string[]), Alias = "options")] public string[] Options { get; set; } = null; [YamlMember(typeof(string[]), Alias = "builds")] public string[] Builds { get; set; } = null; [YamlMember(typeof(string), Alias = "cpuArch")] public string Arch { get; set; } = null; [YamlMember(typeof(bool?), Alias = "onUpgrade")] public bool? OnUpgrade { get; set; } = null; [YamlMember(typeof(string[]), Alias = "onUpgradeVersions")] public string[] OnUpgradeVersions { get; set; } = null; [YamlMember(typeof(string), Alias = "previousOption")] [CanBeNull] public string PreviousOption { get; set; } = null; [YamlMember(typeof(ErrorAction), Alias = "errorAction")] public ErrorAction? ErrorAction { get; set; } = null; [YamlMember(typeof(bool), Alias = "allowRetries")] public bool? AllowRetries { get; set; } = null; /// Null if compatible, otherwise a string representing the error public virtual string? IsISOCompatible() => null; protected bool IsApplicableNumber(string number, int value) { bool negative = false; number = number.Trim(); if (number.StartsWith("!")) { number = number.TrimStart('!'); negative = true; } bool result = false; if (number.StartsWith(">=")) { var parsed = int.Parse(number.Substring(2), CultureInfo.InvariantCulture); if (value >= parsed) result = true; } else if (number.StartsWith("<=")) { var parsed = int.Parse(number.Substring(2), CultureInfo.InvariantCulture); if (value <= parsed) result = true; } else if (number.StartsWith(">")) { var parsed = int.Parse(number.Substring(1), CultureInfo.InvariantCulture); if (value > parsed) result = true; } else if (number.StartsWith("<")) { var parsed = int.Parse(number.Substring(1), CultureInfo.InvariantCulture); if (value < parsed) result = true; } else { var parsed = int.Parse(number, CultureInfo.InvariantCulture); if (value == parsed) result = true; } return negative ? !result : result; } } } ================================================ FILE: TrustedUninstaller.Shared/Tasks/TaskList.cs ================================================ using System.Collections.Generic; namespace TrustedUninstaller.Shared.Tasks { public class TaskList { public List uninstallTasks { get; set; } = new List { }; } } ================================================ FILE: TrustedUninstaller.Shared/Tasks/UninstallTask.cs ================================================ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using JetBrains.Annotations; using TrustedUninstaller.Shared.Parser; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Tasks { public enum PreviousOption { Ignore, } public class UninstallTask { public string Title { get; set; } #nullable enable public string? Description { get; set; } #nullable disable [YamlMember(typeof(ISOSetting), Alias = "iso")] public virtual ISOSetting ISO { get; set; } = ISOSetting.True; [YamlMember(typeof(OOBESetting?), Alias = "oobe")] public virtual OOBESetting? OOBE { get; set; } = OOBESetting.True; public List Actions { get; set; } = new List(); public int Priority { get; set; } = 1; public UninstallTaskPrivilege Privilege { get; set; } = UninstallTaskPrivilege.Admin; [YamlMember(typeof(string), Alias = "option")] public string Option { get; set; } = null; [YamlMember(typeof(string[]), Alias = "options")] public string[] Options { get; set; } = null; [YamlMember(typeof(string[]), Alias = "builds")] public string[] Builds { get; set; } = null; [YamlMember(typeof(string), Alias = "cpuArch")] public string Arch { get; set; } = null; [YamlMember(typeof(bool?), Alias = "onUpgrade")] public bool? OnUpgrade { get; set; } = null; [YamlMember(typeof(string[]), Alias = "onUpgradeVersions")] public string[] OnUpgradeVersions { get; set; } = null; [YamlMember(typeof(string), Alias = "previousOption")] [CanBeNull] public string PreviousOption { get; set; } = null; public List Features { get; set; } = new List(); public List Tasks { set => Features = value; get => Features; } public void Update() { /* var statusList = Actions.Select(entry => entry.GetStatus()).ToList(); if (statusList.Any(entry => entry == UninstallTaskStatus.InProgress)) { Status = UninstallTaskStatus.InProgress; } else if (statusList.All(entry => entry == UninstallTaskStatus.Completed)) { Status = UninstallTaskStatus.Completed; } else { Status = UninstallTaskStatus.ToDo; } */ } } } ================================================ FILE: TrustedUninstaller.Shared/Tasks/UninstallTaskPrivilege.cs ================================================ namespace TrustedUninstaller.Shared.Tasks { public enum UninstallTaskPrivilege { Admin, TrustedInstaller } } ================================================ FILE: TrustedUninstaller.Shared/Tasks/UninstallTaskStatus.cs ================================================ namespace TrustedUninstaller.Shared.Tasks { public enum UninstallTaskStatus { Completed, InProgress, ToDo } } ================================================ FILE: TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj ================================================  Debug AnyCPU {9BDA9D32-E9A1-4DB8-9D90-443792107E28} Library Properties TrustedUninstaller.Shared TrustedUninstaller.Shared v4.7.2 8 512 true *.xml disable $([System.IO.Path]::GetFullPath('$(SolutionDir)'))=./ true bin\x64\Debug\ DEBUG;TRACE full x64 prompt MinimumRecommendedRules.ruleset disable true bin\x64\Release\ TRACE true embedded x64 prompt MinimumRecommendedRules.ruleset disable true bin\x64\Release\ x64 true embedded disable true SINGLE bin\arm64\Release\ arm64 true embedded disable true SINGLE bin\x64\Release\ x64 true disable embedded true SINGLE;DEBUG False True ..\TrustedUninstaller.GUI\bin\x64\Release\YamlDotNet.dll {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} 1 0 0 tlbimp False True {B596CC9F-56E5-419E-A622-E01BB457431E} 2 0 0 tlbimp False True Always en 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") PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'Shared','""""x64\$(Configuration)""""' -NoNewWindow -Wait" .allowedextension 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") ================================================ FILE: TrustedUninstaller.Shared/USB/BZip2.cs ================================================ using System; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.Serialization; namespace iso_mode { public class BZip2 { /// /// Decompress the input writing /// uncompressed data to the output stream /// /// The readable stream containing data to decompress. /// The output stream to receive the decompressed data. /// Both streams are closed on completion if true. public static void Decompress(Stream inStream, Stream outStream, bool isStreamOwner) { if (inStream == null) throw new ArgumentNullException(nameof(inStream)); if (outStream == null) throw new ArgumentNullException(nameof(outStream)); try { using (BZip2InputStream bzipInput = new BZip2InputStream(inStream)) { bzipInput.IsStreamOwner = isStreamOwner; var buffer = new byte[4096]; while (true) { int bytesRead = bzipInput.Read(buffer, 0, buffer.Length); if (bytesRead > 0) { outStream.Write(buffer, 0, bytesRead); } else { outStream.Flush(); break; } } } } finally { if (isStreamOwner) { // inStream is closed by the BZip2InputStream if stream owner outStream.Dispose(); } } } public class BZip2InputStream : Stream { #region Constants private const int START_BLOCK_STATE = 1; private const int RAND_PART_A_STATE = 2; private const int RAND_PART_B_STATE = 3; private const int RAND_PART_C_STATE = 4; private const int NO_RAND_PART_A_STATE = 5; private const int NO_RAND_PART_B_STATE = 6; private const int NO_RAND_PART_C_STATE = 7; #if VECTORIZE_MEMORY_MOVE private static readonly int VectorSize = System.Numerics.Vector.Count; #endif // VECTORIZE_MEMORY_MOVE #endregion Constants #region Instance Fields /*-- index of the last char in the block, so the block size == last + 1. --*/ private int last; /*-- index in zptr[] of original string after sorting. --*/ private int origPtr; /*-- always: in the range 0 .. 9. The current block size is 100000 * this number. --*/ private int blockSize100k; private bool blockRandomised; private int bsBuff; private int bsLive; private IChecksum mCrc = new BZip2Crc(); private bool[] inUse = new bool[256]; private int nInUse; private byte[] seqToUnseq = new byte[256]; private byte[] unseqToSeq = new byte[256]; private byte[] selector = new byte[BZip2Constants.MaximumSelectors]; private byte[] selectorMtf = new byte[BZip2Constants.MaximumSelectors]; private int[] tt; private byte[] ll8; /*-- freq table collected to save a pass over the data during decompression. --*/ private int[] unzftab = new int[256]; private int[][] limit = new int[BZip2Constants.GroupCount][]; private int[][] baseArray = new int[BZip2Constants.GroupCount][]; private int[][] perm = new int[BZip2Constants.GroupCount][]; private int[] minLens = new int[BZip2Constants.GroupCount]; private readonly Stream baseStream; private bool streamEnd; private int currentChar = -1; private int currentState = START_BLOCK_STATE; private int storedBlockCRC, storedCombinedCRC; private int computedBlockCRC; private uint computedCombinedCRC; private int count, chPrev, ch2; private int tPos; private int rNToGo; private int rTPos; private int i2, j2; private byte z; #endregion Instance Fields /// /// Construct instance for reading from stream /// /// Data source public BZip2InputStream(Stream stream) { if (stream == null) throw new ArgumentNullException(nameof(stream)); // init arrays for (int i = 0; i < BZip2Constants.GroupCount; ++i) { limit[i] = new int[BZip2Constants.MaximumAlphaSize]; baseArray[i] = new int[BZip2Constants.MaximumAlphaSize]; perm[i] = new int[BZip2Constants.MaximumAlphaSize]; } baseStream = stream; bsLive = 0; bsBuff = 0; Initialize(); InitBlock(); SetupBlock(); } /// /// Get/set flag indicating ownership of underlying stream. /// When the flag is true will close the underlying stream also. /// public bool IsStreamOwner { get; set; } = true; #region Stream Overrides /// /// Gets a value indicating if the stream supports reading /// public override bool CanRead { get { return baseStream.CanRead; } } /// /// Gets a value indicating whether the current stream supports seeking. /// public override bool CanSeek { get { return false; } } /// /// Gets a value indicating whether the current stream supports writing. /// This property always returns false /// public override bool CanWrite { get { return false; } } /// /// Gets the length in bytes of the stream. /// public override long Length { get { return baseStream.Length; } } /// /// Gets the current position of the stream. /// Setting the position is not supported and will throw a NotSupportException. /// /// Any attempt to set the position. public override long Position { get { return baseStream.Position; } set { throw new NotSupportedException("BZip2InputStream position cannot be set"); } } /// /// Flushes the stream. /// public override void Flush() { baseStream.Flush(); } /// /// Set the streams position. This operation is not supported and will throw a NotSupportedException /// /// A byte offset relative to the parameter. /// A value of type indicating the reference point used to obtain the new position. /// The new position of the stream. /// Any access public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("BZip2InputStream Seek not supported"); } /// /// Sets the length of this stream to the given value. /// This operation is not supported and will throw a NotSupportedExceptionortedException /// /// The new length for the stream. /// Any access public override void SetLength(long value) { throw new NotSupportedException("BZip2InputStream SetLength not supported"); } /// /// Writes a block of bytes to this stream using data from a buffer. /// This operation is not supported and will throw a NotSupportedException /// /// The buffer to source data from. /// The offset to start obtaining data from. /// The number of bytes of data to write. /// Any access public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException("BZip2InputStream Write not supported"); } /// /// Writes a byte to the current position in the file stream. /// This operation is not supported and will throw a NotSupportedException /// /// The value to write. /// Any access public override void WriteByte(byte value) { throw new NotSupportedException("BZip2InputStream WriteByte not supported"); } /// /// Read a sequence of bytes and advances the read position by one byte. /// /// Array of bytes to store values in /// Offset in array to begin storing data /// The maximum number of bytes to read /// The total number of bytes read into the buffer. This might be less /// than the number of bytes requested if that number of bytes are not /// currently available or zero if the end of the stream is reached. /// public override int Read(byte[] buffer, int offset, int count) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } for (int i = 0; i < count; ++i) { int rb = ReadByte(); if (rb == -1) { return i; } buffer[offset + i] = (byte)rb; } return count; } /// /// Closes the stream, releasing any associated resources. /// protected override void Dispose(bool disposing) { if (disposing && IsStreamOwner) { baseStream.Dispose(); } } /// /// Read a byte from stream advancing position /// /// byte read or -1 on end of stream public override int ReadByte() { if (streamEnd) { return -1; // ok } int retChar = currentChar; switch (currentState) { case RAND_PART_B_STATE: SetupRandPartB(); break; case RAND_PART_C_STATE: SetupRandPartC(); break; case NO_RAND_PART_B_STATE: SetupNoRandPartB(); break; case NO_RAND_PART_C_STATE: SetupNoRandPartC(); break; case START_BLOCK_STATE: case NO_RAND_PART_A_STATE: case RAND_PART_A_STATE: break; } return retChar; } #endregion Stream Overrides private void MakeMaps() { nInUse = 0; for (int i = 0; i < 256; ++i) { if (inUse[i]) { seqToUnseq[nInUse] = (byte)i; unseqToSeq[i] = (byte)nInUse; nInUse++; } } } private void Initialize() { char magic1 = BsGetUChar(); char magic2 = BsGetUChar(); char magic3 = BsGetUChar(); char magic4 = BsGetUChar(); if (magic1 != 'B' || magic2 != 'Z' || magic3 != 'h' || magic4 < '1' || magic4 > '9') { streamEnd = true; return; } SetDecompressStructureSizes(magic4 - '0'); computedCombinedCRC = 0; } private void InitBlock() { char magic1 = BsGetUChar(); char magic2 = BsGetUChar(); char magic3 = BsGetUChar(); char magic4 = BsGetUChar(); char magic5 = BsGetUChar(); char magic6 = BsGetUChar(); if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) { Complete(); return; } if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) { BadBlockHeader(); streamEnd = true; return; } storedBlockCRC = BsGetInt32(); blockRandomised = (BsR(1) == 1); GetAndMoveToFrontDecode(); mCrc.Reset(); currentState = START_BLOCK_STATE; } private void EndBlock() { computedBlockCRC = (int)mCrc.Value; // -- A bad CRC is considered a fatal error. -- if (storedBlockCRC != computedBlockCRC) { CrcError(); } // 1528150659 computedCombinedCRC = ((computedCombinedCRC << 1) & 0xFFFFFFFF) | (computedCombinedCRC >> 31); computedCombinedCRC = computedCombinedCRC ^ (uint)computedBlockCRC; } private void Complete() { storedCombinedCRC = BsGetInt32(); if (storedCombinedCRC != (int)computedCombinedCRC) { CrcError(); } streamEnd = true; } private void FillBuffer() { int thech = 0; try { thech = baseStream.ReadByte(); } catch (Exception) { CompressedStreamEOF(); } if (thech == -1) { CompressedStreamEOF(); } bsBuff = (bsBuff << 8) | (thech & 0xFF); bsLive += 8; } private int BsR(int n) { while (bsLive < n) { FillBuffer(); } int v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); bsLive -= n; return v; } private char BsGetUChar() { return (char)BsR(8); } private int BsGetIntVS(int numBits) { return BsR(numBits); } private int BsGetInt32() { int result = BsR(8); result = (result << 8) | BsR(8); result = (result << 8) | BsR(8); result = (result << 8) | BsR(8); return result; } private void RecvDecodingTables() { char[][] len = new char[BZip2Constants.GroupCount][]; for (int i = 0; i < BZip2Constants.GroupCount; ++i) { len[i] = new char[BZip2Constants.MaximumAlphaSize]; } bool[] inUse16 = new bool[16]; //--- Receive the mapping table --- for (int i = 0; i < 16; i++) { inUse16[i] = (BsR(1) == 1); } for (int i = 0; i < 16; i++) { if (inUse16[i]) { for (int j = 0; j < 16; j++) { inUse[i * 16 + j] = (BsR(1) == 1); } } else { for (int j = 0; j < 16; j++) { inUse[i * 16 + j] = false; } } } MakeMaps(); int alphaSize = nInUse + 2; //--- Now the selectors --- int nGroups = BsR(3); int nSelectors = BsR(15); for (int i = 0; i < nSelectors; i++) { int j = 0; while (BsR(1) == 1) { j++; } selectorMtf[i] = (byte)j; } //--- Undo the MTF values for the selectors. --- byte[] pos = new byte[BZip2Constants.GroupCount]; for (int v = 0; v < nGroups; v++) { pos[v] = (byte)v; } for (int i = 0; i < nSelectors; i++) { int v = selectorMtf[i]; byte tmp = pos[v]; while (v > 0) { pos[v] = pos[v - 1]; v--; } pos[0] = tmp; selector[i] = tmp; } //--- Now the coding tables --- for (int t = 0; t < nGroups; t++) { int curr = BsR(5); for (int i = 0; i < alphaSize; i++) { while (BsR(1) == 1) { if (BsR(1) == 0) { curr++; } else { curr--; } } len[t][i] = (char)curr; } } //--- Create the Huffman decoding tables --- for (int t = 0; t < nGroups; t++) { int minLen = 32; int maxLen = 0; for (int i = 0; i < alphaSize; i++) { maxLen = Math.Max(maxLen, len[t][i]); minLen = Math.Min(minLen, len[t][i]); } HbCreateDecodeTables(limit[t], baseArray[t], perm[t], len[t], minLen, maxLen, alphaSize); minLens[t] = minLen; } } private void GetAndMoveToFrontDecode() { byte[] yy = new byte[256]; int nextSym; int limitLast = BZip2Constants.BaseBlockSize * blockSize100k; origPtr = BsGetIntVS(24); RecvDecodingTables(); int EOB = nInUse + 1; int groupNo = -1; int groupPos = 0; /*-- Setting up the unzftab entries here is not strictly necessary, but it does save having to do it later in a separate pass, and so saves a block's worth of cache misses. --*/ for (int i = 0; i <= 255; i++) { unzftab[i] = 0; } for (int i = 0; i <= 255; i++) { yy[i] = (byte)i; } last = -1; if (groupPos == 0) { groupNo++; groupPos = BZip2Constants.GroupSize; } groupPos--; int zt = selector[groupNo]; int zn = minLens[zt]; int zvec = BsR(zn); int zj; while (zvec > limit[zt][zn]) { if (zn > 20) { // the longest code throw new BZip2Exception("Bzip data error"); } zn++; while (bsLive < 1) { FillBuffer(); } zj = (bsBuff >> (bsLive - 1)) & 1; bsLive--; zvec = (zvec << 1) | zj; } if (zvec - baseArray[zt][zn] < 0 || zvec - baseArray[zt][zn] >= BZip2Constants.MaximumAlphaSize) { throw new BZip2Exception("Bzip data error"); } nextSym = perm[zt][zvec - baseArray[zt][zn]]; while (true) { if (nextSym == EOB) { break; } if (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB) { int s = -1; int n = 1; do { if (nextSym == BZip2Constants.RunA) { s += (0 + 1) * n; } else if (nextSym == BZip2Constants.RunB) { s += (1 + 1) * n; } n <<= 1; if (groupPos == 0) { groupNo++; groupPos = BZip2Constants.GroupSize; } groupPos--; zt = selector[groupNo]; zn = minLens[zt]; zvec = BsR(zn); while (zvec > limit[zt][zn]) { zn++; while (bsLive < 1) { FillBuffer(); } zj = (bsBuff >> (bsLive - 1)) & 1; bsLive--; zvec = (zvec << 1) | zj; } nextSym = perm[zt][zvec - baseArray[zt][zn]]; } while (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB); s++; byte ch = seqToUnseq[yy[0]]; unzftab[ch] += s; while (s > 0) { last++; ll8[last] = ch; s--; } if (last >= limitLast) { BlockOverrun(); } continue; } else { last++; if (last >= limitLast) { BlockOverrun(); } byte tmp = yy[nextSym - 1]; unzftab[seqToUnseq[tmp]]++; ll8[last] = seqToUnseq[tmp]; var j = nextSym - 1; #if VECTORIZE_MEMORY_MOVE // This is vectorized memory move. Going from the back, we're taking chunks of array // and write them at the new location shifted by one. Since chunks are VectorSize long, // at the end we have to move "tail" (or head actually) of the array using a plain loop. // If System.Numerics.Vector API is not available, the plain loop is used to do the whole copying. while(j >= VectorSize) { var arrayPart = new System.Numerics.Vector(yy, j - VectorSize); arrayPart.CopyTo(yy, j - VectorSize + 1); j -= VectorSize; } #endif // VECTORIZE_MEMORY_MOVE while (j > 0) { yy[j] = yy[--j]; } yy[0] = tmp; if (groupPos == 0) { groupNo++; groupPos = BZip2Constants.GroupSize; } groupPos--; zt = selector[groupNo]; zn = minLens[zt]; zvec = BsR(zn); while (zvec > limit[zt][zn]) { zn++; while (bsLive < 1) { FillBuffer(); } zj = (bsBuff >> (bsLive - 1)) & 1; bsLive--; zvec = (zvec << 1) | zj; } nextSym = perm[zt][zvec - baseArray[zt][zn]]; continue; } } } private void SetupBlock() { int[] cftab = new int[257]; cftab[0] = 0; Array.Copy(unzftab, 0, cftab, 1, 256); for (int i = 1; i <= 256; i++) { cftab[i] += cftab[i - 1]; } for (int i = 0; i <= last; i++) { byte ch = ll8[i]; tt[cftab[ch]] = i; cftab[ch]++; } cftab = null; tPos = tt[origPtr]; count = 0; i2 = 0; ch2 = 256; /*-- not a char and not EOF --*/ if (blockRandomised) { rNToGo = 0; rTPos = 0; SetupRandPartA(); } else { SetupNoRandPartA(); } } private void SetupRandPartA() { if (i2 <= last) { chPrev = ch2; ch2 = ll8[tPos]; tPos = tt[tPos]; if (rNToGo == 0) { rNToGo = BZip2Constants.RandomNumbers[rTPos]; rTPos++; if (rTPos == 512) { rTPos = 0; } } rNToGo--; ch2 ^= (int)((rNToGo == 1) ? 1 : 0); i2++; currentChar = ch2; currentState = RAND_PART_B_STATE; mCrc.Update(ch2); } else { EndBlock(); InitBlock(); SetupBlock(); } } private void SetupNoRandPartA() { if (i2 <= last) { chPrev = ch2; ch2 = ll8[tPos]; tPos = tt[tPos]; i2++; currentChar = ch2; currentState = NO_RAND_PART_B_STATE; mCrc.Update(ch2); } else { EndBlock(); InitBlock(); SetupBlock(); } } private void SetupRandPartB() { if (ch2 != chPrev) { currentState = RAND_PART_A_STATE; count = 1; SetupRandPartA(); } else { count++; if (count >= 4) { z = ll8[tPos]; tPos = tt[tPos]; if (rNToGo == 0) { rNToGo = BZip2Constants.RandomNumbers[rTPos]; rTPos++; if (rTPos == 512) { rTPos = 0; } } rNToGo--; z ^= (byte)((rNToGo == 1) ? 1 : 0); j2 = 0; currentState = RAND_PART_C_STATE; SetupRandPartC(); } else { currentState = RAND_PART_A_STATE; SetupRandPartA(); } } } private void SetupRandPartC() { if (j2 < (int)z) { currentChar = ch2; mCrc.Update(ch2); j2++; } else { currentState = RAND_PART_A_STATE; i2++; count = 0; SetupRandPartA(); } } private void SetupNoRandPartB() { if (ch2 != chPrev) { currentState = NO_RAND_PART_A_STATE; count = 1; SetupNoRandPartA(); } else { count++; if (count >= 4) { z = ll8[tPos]; tPos = tt[tPos]; currentState = NO_RAND_PART_C_STATE; j2 = 0; SetupNoRandPartC(); } else { currentState = NO_RAND_PART_A_STATE; SetupNoRandPartA(); } } } private void SetupNoRandPartC() { if (j2 < (int)z) { currentChar = ch2; mCrc.Update(ch2); j2++; } else { currentState = NO_RAND_PART_A_STATE; i2++; count = 0; SetupNoRandPartA(); } } private void SetDecompressStructureSizes(int newSize100k) { if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k && blockSize100k <= 9)) { throw new BZip2Exception("Invalid block size"); } blockSize100k = newSize100k; if (newSize100k == 0) { return; } int n = BZip2Constants.BaseBlockSize * newSize100k; ll8 = new byte[n]; tt = new int[n]; } private static void CompressedStreamEOF() { throw new EndOfStreamException("BZip2 input stream end of compressed stream"); } private static void BlockOverrun() { throw new BZip2Exception("BZip2 input stream block overrun"); } private static void BadBlockHeader() { throw new BZip2Exception("BZip2 input stream bad block header"); } private static void CrcError() { throw new BZip2Exception("BZip2 input stream crc error"); } private static void HbCreateDecodeTables(int[] limit, int[] baseArray, int[] perm, char[] length, int minLen, int maxLen, int alphaSize) { int pp = 0; for (int i = minLen; i <= maxLen; ++i) { for (int j = 0; j < alphaSize; ++j) { if (length[j] == i) { perm[pp] = j; ++pp; } } } for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) { baseArray[i] = 0; } for (int i = 0; i < alphaSize; i++) { ++baseArray[length[i] + 1]; } for (int i = 1; i < BZip2Constants.MaximumCodeLength; i++) { baseArray[i] += baseArray[i - 1]; } for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) { limit[i] = 0; } int vec = 0; for (int i = minLen; i <= maxLen; i++) { vec += (baseArray[i + 1] - baseArray[i]); limit[i] = vec - 1; vec <<= 1; } for (int i = minLen + 1; i <= maxLen; i++) { baseArray[i] = ((limit[i - 1] + 1) << 1) - baseArray[i]; } } } /// /// CRC-32 with unreversed data and reversed output /// /// /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. /// /// Polynomials over GF(2) are represented in binary, one bit per coefficient, /// with the lowest powers in the most significant bit. Then adding polynomials /// is just exclusive-or, and multiplying a polynomial by x is a right shift by /// one. If we call the above polynomial p, and represent a byte as the /// polynomial q, also with the lowest power in the most significant bit (so the /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, /// where a mod b means the remainder after dividing a by b. /// /// This calculation is done using the shift-register method of multiplying and /// taking the remainder. The register is initialized to zero, and for each /// incoming bit, x^32 is added mod p to the register if the bit is a one (where /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by /// x (which is shifting right by one and adding x^32 mod p if the bit shifted /// out is a one). We start with the highest power (least significant bit) of /// q and repeat for all eight bits of q. /// /// This implementation uses sixteen lookup tables stored in one linear array /// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8 /// algorithm described in this Intel white paper: /// /// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf /// /// The first lookup table is simply the CRC of all possible eight bit values. /// Each successive lookup table is derived from the original table generated /// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs /// together will produce the same output as a byte-by-byte CRC loop with /// fewer arithmetic and bit manipulation operations, at the cost of increased /// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table, /// which is still small enough to fit in most processors' L1 cache.) /// public sealed class BZip2Crc : IChecksum { #region Instance Fields private const uint crcInit = 0xFFFFFFFF; //const uint crcXor = 0x00000000; private static readonly uint[] crcTable = CrcUtilities.GenerateSlicingLookupTable(0x04C11DB7, isReversed: false); /// /// The CRC data checksum so far. /// private uint checkValue; #endregion Instance Fields /// /// Initialise a default instance of /// public BZip2Crc() { Reset(); } /// /// Resets the CRC data checksum as if no update was ever called. /// public void Reset() { checkValue = crcInit; } /// /// Returns the CRC data checksum computed so far. /// /// Reversed Out = true public long Value { get { // Technically, the output should be: //return (long)(~checkValue ^ crcXor); // but x ^ 0 = x, so there is no point in adding // the XOR operation return (long)(~checkValue); } } /// /// Updates the checksum with the int bval. /// /// /// the byte is taken as the lower 8 bits of bval /// /// Reversed Data = false [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(int bval) { checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8)); } /// /// Updates the CRC data checksum with the bytes taken from /// a block of data. /// /// Contains the data to update the CRC with. public void Update(byte[] buffer) { if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } Update(buffer, 0, buffer.Length); } /// /// Update CRC data checksum based on a portion of a block of data /// /// /// The chunk of data to add /// public void Update(ArraySegment segment) { Update(segment.Array, segment.Offset, segment.Count); } /// /// Internal helper function for updating a block of data using slicing. /// /// The array containing the data to add /// Range start for (inclusive) /// The number of bytes to checksum starting from private void Update(byte[] data, int offset, int count) { int remainder = count % CrcUtilities.SlicingDegree; int end = offset + count - remainder; while (offset != end) { checkValue = CrcUtilities.UpdateDataForNormalPoly(data, offset, crcTable, checkValue); offset += CrcUtilities.SlicingDegree; } if (remainder != 0) { SlowUpdateLoop(data, offset, end + remainder); } } /// /// A non-inlined function for updating data that doesn't fit in a 16-byte /// block. We don't expect to enter this function most of the time, and when /// we do we're not here for long, so disabling inlining here improves /// performance overall. /// /// The array containing the data to add /// Range start for (inclusive) /// Range end for (exclusive) [MethodImpl(MethodImplOptions.NoInlining)] private void SlowUpdateLoop(byte[] data, int offset, int end) { while (offset != end) { Update(data[offset++]); } } } internal static class CrcUtilities { /// /// The number of slicing lookup tables to generate. /// internal const int SlicingDegree = 16; /// /// Generates multiple CRC lookup tables for a given polynomial, stored /// in a linear array of uints. The first block (i.e. the first 256 /// elements) is the same as the byte-by-byte CRC lookup table. /// /// The generating CRC polynomial /// Whether the polynomial is in reversed bit order /// A linear array of 256 * elements /// /// This table could also be generated as a rectangular array, but the /// JIT compiler generates slower code than if we use a linear array. /// Known issue, see: https://github.com/dotnet/runtime/issues/30275 /// internal static uint[] GenerateSlicingLookupTable(uint polynomial, bool isReversed) { var table = new uint[256 * SlicingDegree]; uint one = isReversed ? 1 : (1U << 31); for (int i = 0; i < 256; i++) { uint res = (uint)(isReversed ? i : i << 24); for (int j = 0; j < SlicingDegree; j++) { for (int k = 0; k < 8; k++) { if (isReversed) { res = (res & one) == 1 ? polynomial ^ (res >> 1) : res >> 1; } else { res = (res & one) != 0 ? polynomial ^ (res << 1) : res << 1; } } table[(256 * j) + i] = res; } } return table; } /// /// Mixes the first four bytes of input with /// using normal ordering before calling . /// /// Array of data to checksum /// Offset to start reading from /// The table to use for slicing-by-16 lookup /// Checksum state before this update call /// A new unfinalized checksum value /// /// /// Assumes input[offset]..input[offset + 15] are valid array indexes. /// For performance reasons, this must be checked by the caller. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint UpdateDataForNormalPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) { byte x1 = (byte)((byte)(checkValue >> 24) ^ input[offset]); byte x2 = (byte)((byte)(checkValue >> 16) ^ input[offset + 1]); byte x3 = (byte)((byte)(checkValue >> 8) ^ input[offset + 2]); byte x4 = (byte)((byte)checkValue ^ input[offset + 3]); return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); } /// /// Mixes the first four bytes of input with /// using reflected ordering before calling . /// /// Array of data to checksum /// Offset to start reading from /// The table to use for slicing-by-16 lookup /// Checksum state before this update call /// A new unfinalized checksum value /// /// /// Assumes input[offset]..input[offset + 15] are valid array indexes. /// For performance reasons, this must be checked by the caller. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint UpdateDataForReversedPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) { byte x1 = (byte)((byte)checkValue ^ input[offset]); byte x2 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 1]); byte x3 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 2]); byte x4 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 3]); return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); } /// /// A shared method for updating an unfinalized CRC checksum using slicing-by-16. /// /// Array of data to checksum /// Offset to start reading from /// The table to use for slicing-by-16 lookup /// First byte of input after mixing with the old CRC /// Second byte of input after mixing with the old CRC /// Third byte of input after mixing with the old CRC /// Fourth byte of input after mixing with the old CRC /// A new unfinalized checksum value /// /// /// Even though the first four bytes of input are fed in as arguments, /// should be the same value passed to this /// function's caller (either or /// ). This method will get inlined /// into both functions, so using the same offset produces faster code. /// /// /// Because most processors running C# have some kind of instruction-level /// parallelism, the order of XOR operations can affect performance. This /// ordering assumes that the assembly code generated by the just-in-time /// compiler will emit a bunch of arithmetic operations for checking array /// bounds. Then it opportunistically XORs a1 and a2 to keep the processor /// busy while those other parts of the pipeline handle the range check /// calculations. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint UpdateDataCommon(byte[] input, int offset, uint[] crcTable, byte x1, byte x2, byte x3, byte x4) { uint result; uint a1 = crcTable[x1 + 3840] ^ crcTable[x2 + 3584]; uint a2 = crcTable[x3 + 3328] ^ crcTable[x4 + 3072]; result = crcTable[input[offset + 4] + 2816]; result ^= crcTable[input[offset + 5] + 2560]; a1 ^= crcTable[input[offset + 9] + 1536]; result ^= crcTable[input[offset + 6] + 2304]; result ^= crcTable[input[offset + 7] + 2048]; result ^= crcTable[input[offset + 8] + 1792]; a2 ^= crcTable[input[offset + 13] + 512]; result ^= crcTable[input[offset + 10] + 1280]; result ^= crcTable[input[offset + 11] + 1024]; result ^= crcTable[input[offset + 12] + 768]; result ^= a1; result ^= crcTable[input[offset + 14] + 256]; result ^= crcTable[input[offset + 15]]; result ^= a2; return result; } } /// /// Interface to compute a data checksum used by checked input/output streams. /// A data checksum can be updated by one byte or with a byte array. After each /// update the value of the current checksum can be returned by calling /// getValue. The complete checksum object can also be reset /// so it can be used again with new data. /// public interface IChecksum { /// /// Resets the data checksum as if no update was ever called. /// void Reset(); /// /// Returns the data checksum computed so far. /// long Value { get; } /// /// Adds one byte to the data checksum. /// /// /// the data value to add. The high byte of the int is ignored. /// void Update(int bval); /// /// Updates the data checksum with the bytes taken from the array. /// /// /// buffer an array of bytes /// void Update(byte[] buffer); /// /// Adds the byte array to the data checksum. /// /// /// The chunk of data to add /// void Update(ArraySegment segment); } /// /// BZip2Exception represents exceptions specific to BZip2 classes and code. /// [Serializable] public class BZip2Exception : Exception { /// /// Initialise a new instance of . /// public BZip2Exception() { } /// /// Initialise a new instance of with its message string. /// /// A that describes the error. public BZip2Exception(string message) : base(message) { } /// /// Initialise a new instance of . /// /// A that describes the error. /// The that caused this exception. public BZip2Exception(string message, Exception innerException) : base(message, innerException) { } /// /// Initializes a new instance of the BZip2Exception class with serialized data. /// /// /// The System.Runtime.Serialization.SerializationInfo that holds the serialized /// object data about the exception being thrown. /// /// /// The System.Runtime.Serialization.StreamingContext that contains contextual information /// about the source or destination. /// protected BZip2Exception(SerializationInfo info, StreamingContext context) : base(info, context) { } } internal static class BZip2Constants { /// /// Random numbers used to randomise repetitive blocks /// public readonly static int[] RandomNumbers = { 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, 936, 638 }; /// /// When multiplied by compression parameter (1-9) gives the block size for compression /// 9 gives the best compression but uses the most memory. /// public const int BaseBlockSize = 100000; /// /// Backend constant /// public const int MaximumAlphaSize = 258; /// /// Backend constant /// public const int MaximumCodeLength = 23; /// /// Backend constant /// public const int RunA = 0; /// /// Backend constant /// public const int RunB = 1; /// /// Backend constant /// public const int GroupCount = 6; /// /// Backend constant /// public const int GroupSize = 50; /// /// Backend constant /// public const int NumberOfIterations = 4; /// /// Backend constant /// public const int MaximumSelectors = (2 + (900000 / GroupSize)); /// /// Backend constant /// public const int OvershootBytes = 20; } } } ================================================ FILE: TrustedUninstaller.Shared/USB/Drive.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using Core; using Microsoft.Win32; using Microsoft.Win32.SafeHandles; using static iso_mode.Win32; using FileMode = System.IO.FileMode; using FileShare = System.IO.FileShare; namespace iso_mode { internal class Drive { internal class VolumeNameAndLetter { internal string VolumeName = null; internal char? Letter = null; } private static char GetNextAvailableDriveLetter() { var letters = new List() { 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B' }; foreach (var driveInfo in DriveInfo.GetDrives()) { letters.Remove(driveInfo.Name[0]); } foreach (var userKey in Registry.Users.GetSubKeyNames()) { var networkKey = Registry.Users.OpenSubKey(userKey + @"\Network"); if (networkKey == null) continue; foreach (var networkLetter in networkKey.GetSubKeyNames().Where(x => x.Length == 1)) { if (letters.Count > 1) letters.Remove(networkLetter[0]); } } return letters.First(); } private static MEDIA_TYPE _mediaType = 0; //Returns true if drive Index is successfully created //Returns false if not created successfully internal static bool CreatePartition(uint driveIndex, long size, long secondsize = 0) { SafeFileHandle handle = GetDriveHandle(driveIndex); if (handle.DangerousGetHandle() == INVALID_HANDLE_VALUE) { return false; } uint returned; //Console.WriteLine("FSCTL_LOCK_VOLUME: " + DeviceIoControl(handle, 0x00090018, IntPtr.Zero, 0, IntPtr.Zero, //0, out uint returned, IntPtr.Zero)); //Console.WriteLine("FSCTL_DISMOUNT_VOLUME: " + DeviceIoControl(handle, IoCtl.FSCTL_DISMOUNT_VOLUME, //IntPtr.Zero, 0, IntPtr.Zero, 0, out returned, IntPtr.Zero)); Console.WriteLine("DISK_DELETE_DRIVE_LAYOUT: " + DeviceIoControl(handle, IoCtl.DISK_DELETE_DRIVE_LAYOUT, IntPtr.Zero, 0, IntPtr.Zero, 0, out returned, IntPtr.Zero)); Console.WriteLine("DISK_UPDATE_PROPERTIES: " + DeviceIoControl(handle, IoCtl.DISK_UPDATE_PROPERTIES, IntPtr.Zero, 0, IntPtr.Zero, 0, out returned, IntPtr.Zero)); //Step 2: IoCtl.DISK_GET_DRIVE_GEOMETRY_EX to get the physical disk's geometry ( we need some information in it to fill partition data) //The number of surfaces (or heads, which is the same thing), cylinders, and sectors vary a lot; the specification of the number of each is called the geometry of a hard disk. //The geometry is usually stored in a special, battery-powered memory location called the CMOS RAM , from where the operating system can fetch it during bootup or driver initialization. var geometry = GetGeometry(handle); //Step 3: IoCtl.DISK_CREATE_DISK is used to initialize a disk with an empty partition table. CREATE_DISK createDisk = new CREATE_DISK(); createDisk.PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_MBR; createDisk.Mbr.Signature = 0x80; using (var createDiskBuffer = new SafeHGlobalHandle(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CREATE_DISK))))) { Marshal.StructureToPtr(createDisk, createDiskBuffer.DangerousGetHandle(), false); FillMemory(createDiskBuffer.DangerousGetHandle(), (uint)Marshal.SizeOf(typeof(CREATE_DISK)), 0); byte[] arr1 = new byte[Marshal.SizeOf(typeof(CREATE_DISK))]; Marshal.Copy(createDiskBuffer.DangerousGetHandle(), arr1, 0, Marshal.SizeOf(typeof(CREATE_DISK))); DeviceIoControl(handle, IoCtl.DISK_CREATE_DISK, createDiskBuffer.DangerousGetHandle(), (uint)Marshal.SizeOf(typeof(CREATE_DISK)), IntPtr.Zero, 0, out _, IntPtr.Zero); } DeviceIoControl(handle, IoCtl.DISK_UPDATE_PROPERTIES, IntPtr.Zero, 0, IntPtr.Zero, 0, out returned, IntPtr.Zero); //Step 4: IoCtl.DISK_SET_DRIVE_LAYOUT_EX to repartition a disk as specified. //Note: use IoCtl.DISK_UPDATE_PROPERTIES to synchronize system view after IoCtl.DISK_CREATE_DISK and IoCtl.DISK_SET_DRIVE_LAYOUT_EX /* DWORD driveLayoutSize = sizeof(DRIVE_LAYOUT_INFORMATION_EX) + sizeof(PARTITION_INFORMATION_EX) * 4 * 25; DRIVE_LAYOUT_INFORMATION_EX *DriveLayoutEx = (DRIVE_LAYOUT_INFORMATION_EX *) new BYTE[driveLayoutSize];*/ Console.WriteLine(Marshal.SizeOf(typeof(DRIVE_LAYOUT_INFORMATION_EX))); var layoutSize = Marshal.SizeOf(typeof(DRIVE_LAYOUT_INFORMATION_EX)); //var driveLayoutBuffer = Marshal.AllocHGlobal(layoutSize + 4); //FillMemory(driveLayoutBuffer, (uint)layoutSize * 4, 0); DRIVE_LAYOUT_INFORMATION_EX driveLayoutEx = new DRIVE_LAYOUT_INFORMATION_EX(); int pn = 0; driveLayoutEx.PartitionEntry = new PARTITION_INFORMATION_EX[4]; _mediaType = (MEDIA_TYPE)geometry.Geometry.MediaType; Int64 bytes_per_track = (geometry.Geometry.SectorsPerTrack) * (geometry.Geometry.BytesPerSector); Console.WriteLine("MediaType: '" + _mediaType + "'"); Console.WriteLine("SectorsPerTrack: '" + geometry.Geometry.SectorsPerTrack + "'"); Console.WriteLine("BytesPerSector: '" + geometry.Geometry.BytesPerSector + "'"); Console.WriteLine("SectorsPerTrack * BytesPerSector: '" + bytes_per_track + "'"); Console.WriteLine("" + "(^ bytes_per_track)" + ""); driveLayoutEx.PartitionEntry[pn].StartingOffset = bytes_per_track; Console.WriteLine("\n0x123 Offset UINT: '" + driveLayoutEx.PartitionEntry[pn].StartingOffset + "'"); Int64 main_part_size_in_sectors, extra_part_size_in_sectors = 0; main_part_size_in_sectors = (geometry.DiskSize.QuadPart - driveLayoutEx.PartitionEntry[pn].StartingOffset) / geometry.Geometry.BytesPerSector; Console.WriteLine("\nDiskSize: '" + geometry.DiskSize.QuadPart + "'"); Console.WriteLine("DiskSize - Offset: '" + (geometry.DiskSize.QuadPart - driveLayoutEx.PartitionEntry[pn].StartingOffset) + "'"); Console.WriteLine("DiskSize - Offset / BytesPerSector: '" + main_part_size_in_sectors + "'"); Console.WriteLine("" + "(^ main_part_size_in_sectors)" + ""); if (main_part_size_in_sectors <= 0) { return false; } extra_part_size_in_sectors = (MIN_EXTRA_PART_SIZE + bytes_per_track - 1) / bytes_per_track; Console.WriteLine("\nMIN_EXTRA_PART_SIZE: '" + MIN_EXTRA_PART_SIZE + "'"); Console.WriteLine("MIN_EXTRA + BytesPerTrack - 1 / BytesPerTrack: '" + extra_part_size_in_sectors + "'"); Console.WriteLine("" + "(^ extra_part_size_in_sectors)" + ""); main_part_size_in_sectors = ((main_part_size_in_sectors / geometry.Geometry.SectorsPerTrack) - extra_part_size_in_sectors) * geometry.Geometry.SectorsPerTrack; Console.WriteLine("\n((main_part_size / SectorsPerTrack) - extra_part_size) * SectorsPerTrack: '" + main_part_size_in_sectors + "'"); Console.WriteLine("" + "(^ main_part_size_in_sectors (overwrite))" + ""); if (main_part_size_in_sectors <= 0) { return false; } long partlength = (uint)Math.Ceiling((double)(size + (size / 10)) / 4096); Console.WriteLine("PartLength: " + partlength); Console.WriteLine("Size: " + size); Console.WriteLine("Size 4096: " + (partlength * 4096)); partlength = partlength * 4096; while (partlength % bytes_per_track != 0) { partlength++; } Console.WriteLine(partlength); Console.WriteLine("DiskSize Divided Stats: " + $"{geometry.DiskSize.QuadPart} / {bytes_per_track}"); Console.WriteLine("DiskSize Divided: " + geometry.DiskSize.QuadPart / bytes_per_track); Console.WriteLine("DiskSize Divided Stats 2: " + $"({geometry.DiskSize.QuadPart} - {bytes_per_track} - {partlength}) / {bytes_per_track}"); Console.WriteLine("DiskSize Divided 2: " + (geometry.DiskSize.QuadPart - bytes_per_track - partlength) / bytes_per_track); driveLayoutEx.PartitionEntry[pn].PartitionLength = ((partlength)); driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_MBR; driveLayoutEx.PartitionEntry[pn].Mbr.BootIndicator = true; driveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x07; driveLayoutEx.PartitionEntry[pn].PartitionNumber = (uint)pn + 1; driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_MBR; driveLayoutEx.PartitionEntry[pn].RewritePartition = true; driveLayoutEx.PartitionEntry[pn].Mbr.HiddenSectors = (uint)(bytes_per_track / geometry.Geometry.BytesPerSector); driveLayoutEx.PartitionEntry[pn].Mbr.RecognizedPartition = true; var partlength2 = (geometry.DiskSize.QuadPart / 2) - bytes_per_track - partlength; if (partlength2 - bytes_per_track >= 10000000) { pn++; // Set the optional extra partition // Should end on a track boundary while (partlength2 % bytes_per_track != 0) { partlength2--; } driveLayoutEx.PartitionEntry[pn].StartingOffset = bytes_per_track + partlength; driveLayoutEx.PartitionEntry[pn].PartitionLength = partlength2; //TODO: Has to change driveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x07; driveLayoutEx.PartitionEntry[pn].PartitionNumber = (uint)pn + 1; driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_MBR; driveLayoutEx.PartitionEntry[pn].RewritePartition = true; driveLayoutEx.PartitionEntry[pn].Mbr.HiddenSectors = (uint)(bytes_per_track / geometry.Geometry.BytesPerSector); driveLayoutEx.PartitionEntry[pn].Mbr.RecognizedPartition = true; } driveLayoutEx.PartitionEntry[2].PartitionNumber = 3; driveLayoutEx.PartitionEntry[2].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_MBR; driveLayoutEx.PartitionEntry[2].Mbr.PartitionType = 0x00; driveLayoutEx.PartitionEntry[3].PartitionNumber = 4; driveLayoutEx.PartitionEntry[3].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_MBR; driveLayoutEx.PartitionEntry[3].Mbr.PartitionType = 0x00; Console.WriteLine(pn); driveLayoutEx.PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_MBR; driveLayoutEx.PartitionCount = 4; //It should be a multiple of 4 driveLayoutEx.Mbr.Mbr.Signature = 0x80; var bytes = new byte[layoutSize]; var layoutHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); ZeroMemory(layoutHandle.AddrOfPinnedObject(), (uint)layoutSize); Marshal.StructureToPtr(driveLayoutEx, layoutHandle.AddrOfPinnedObject(), false); Console.WriteLine("\nDISK_SET_DRIVE_LAYOUT_EX: " + DeviceIoControl(handle, IoCtl.DISK_SET_DRIVE_LAYOUT_EX, layoutHandle.AddrOfPinnedObject(), 624, IntPtr.Zero, 0, out returned, IntPtr.Zero) + ": " + Marshal.GetLastWin32Error()); layoutHandle.Free(); Console.WriteLine("DISK_UPDATE_PROPERTIES: " + DeviceIoControl(handle, IoCtl.DISK_UPDATE_PROPERTIES, IntPtr.Zero, 0, IntPtr.Zero, 0, out returned, IntPtr.Zero) + ": " + Marshal.GetLastWin32Error()); //Console.WriteLine("FSCTL_UNLOCK_VOLUME: " + DeviceIoControl(handle, IoCtl.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, //0, IntPtr.Zero, (uint)Marshal.SizeOf(typeof(DISK_GEOMETRY_EX)), out returned, IntPtr.Zero)); handle.Dispose(); return true; } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetVolumePathNamesForVolumeNameW([MarshalAs(UnmanagedType.LPWStr)] string lpszVolumeName, [MarshalAs(UnmanagedType.LPWStr)] [Out] StringBuilder lpszVolumeNamePaths, uint cchBuferLength, ref UInt32 lpcchReturnLength); [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetVolumeNameForVolumeMountPoint(string lpszVolumeMountPoint, [Out] StringBuilder lpszVolumeName, uint cchBufferLength); [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetVolumeMountPoint(string lpszVolumeMountPoint, string lpszVolumeName); [DllImport("kernel32.dll")] static extern bool DeleteVolumeMountPoint(string lpszVolumeMountPoint); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr FindFirstVolume([Out] StringBuilder lpszVolumeName, uint cchBufferLength); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool FindNextVolume(IntPtr hFindVolume, [Out] StringBuilder lpszVolumeName, uint cchBufferLength); [DllImport("kernel32.dll")] internal static extern bool DefineDosDevice(uint dwFlags, string lpDeviceName, string lpTargetPath); [DllImport("Kernel32.dll")] internal static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, uint ucchMax); internal const uint DDD_RAW_TARGET_PATH = 0x00000001; internal const uint DDD_REMOVE_DEFINITION = 0x00000002; internal const uint DDD_EXACT_MATCH_ON_REMOVE = 0x00000004; internal const uint DDD_NO_BROADCAST_SYSTEM = 0x00000008; [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FindVolumeClose(IntPtr findVolumeHandle); public static VolumeNameAndLetter GetVolumeMount(USB.UsbDisk usb, long partitionOffset, bool getDriveLetter = true, bool throwIfNoLetterFound = true) { var result = new VolumeNameAndLetter(); for (int i = 0; i < 20; i++) { Thread.Sleep(500); var volumeName = new StringBuilder(1024, 1024); var volumeHandle = FindFirstVolume(volumeName, 1024); do { var volume = volumeName.ToString(); VOLUME_DISK_EXTENTS diskExtents = null; IntPtr outBuffer = IntPtr.Zero; Wrap.RetryExponential.Execute(() => { using (var handle = GetHandle(volumeName.ToString().TrimEnd('\\'))) { diskExtents = new VOLUME_DISK_EXTENTS(); var outBufferSize = (UInt32)Marshal.SizeOf(diskExtents); outBuffer = Marshal.AllocHGlobal((int)outBufferSize); if (!DeviceIoControl(handle, IoCtl.VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, outBuffer, outBufferSize, out uint returned, IntPtr.Zero)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } }); Marshal.PtrToStructure(outBuffer, diskExtents); if (diskExtents!.Extents.DiskNumber == 0 || diskExtents.Extents.DiskNumber != usb.Index) { continue; } if (diskExtents.Extents.StartingOffset == partitionOffset) { result.VolumeName = volume; if (!getDriveLetter) { FindVolumeClose(volumeHandle); return result; } var mountPoint = new StringBuilder(1024, 1024); var returnLength = (uint)0; if (GetVolumePathNamesForVolumeNameW(volume, mountPoint, 1024, ref returnLength) && mountPoint.Length > 0 && VerifyLetterMatches(mountPoint[0], usb, partitionOffset)) { result.Letter = mountPoint[0]; FindVolumeClose(volumeHandle); return result; } var availableLetter = GetNextAvailableDriveLetter(); if (volume.StartsWith(@"\\?\GLOBALROOT", StringComparison.OrdinalIgnoreCase)) { if (!DefineDosDevice(DDD_RAW_TARGET_PATH | DDD_NO_BROADCAST_SYSTEM, availableLetter + ":", volume.Substring(14))) continue; result.Letter = availableLetter; FindVolumeClose(volumeHandle); return result; } if (!SetVolumeMountPoint(availableLetter + @":\", volume)) { if (Marshal.GetLastWin32Error() != 145) continue; if (GetVolumePathNamesForVolumeNameW(volume, mountPoint, 1024, ref returnLength) && mountPoint.Length > 0 && VerifyLetterMatches(mountPoint[0], usb, partitionOffset)) { result.Letter = mountPoint[0]; } } else result.Letter = availableLetter; FindVolumeClose(volumeHandle); return result; } } while (FindNextVolume(volumeHandle, volumeName, 1024)); FindVolumeClose(volumeHandle); } if (result.VolumeName == null) throw new FileNotFoundException("Volume not found for partition."); if (throwIfNoLetterFound) throw new FileNotFoundException("Drive letter not found for partition."); return result; } public static void RemoveMounts(USB.UsbDisk usb) { for (int i = 0; i < 20; i++) { var volumeName = new StringBuilder(1024, 1024); var volumeHandle = FindFirstVolume(volumeName, 1024); do { var volume = volumeName.ToString(); VOLUME_DISK_EXTENTS diskExtents; IntPtr outBuffer; using (var handle = GetHandle(volumeName.ToString().TrimEnd('\\'))) { diskExtents = new Win32.VOLUME_DISK_EXTENTS(); var outBufferSize = (UInt32)Marshal.SizeOf(diskExtents); outBuffer = Marshal.AllocHGlobal((int)outBufferSize); if (!DeviceIoControl(handle, Win32.IoCtl.VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, outBuffer, outBufferSize, out uint returned, IntPtr.Zero)) { continue; } } Marshal.PtrToStructure(outBuffer, diskExtents); if (diskExtents.Extents.DiskNumber == 0 || diskExtents.Extents.DiskNumber != usb.Index) continue; var mountPoint = new StringBuilder(1024, 1024); var returnLength = (uint)0; if (GetVolumePathNamesForVolumeNameW(volume, mountPoint, 1024, ref returnLength) && mountPoint.Length > 0 && VerifyLetterMatches(mountPoint[0], usb, 0)) { var letter = mountPoint[0]; DefineDosDevice(DDD_REMOVE_DEFINITION, letter + ":", null); DeleteVolumeMountPoint(letter + @":\"); } } while (FindNextVolume(volumeHandle, volumeName, 1024)); FindVolumeClose(volumeHandle); } } public static SafeFileHandle GetVolumeHandle(USB.UsbDisk usb, long partitionOffset) { for (int i = 0; i < 20; i++) { var volumeName = new StringBuilder(1024, 1024); var volumeHandle = FindFirstVolume(volumeName, 1024); do { var volume = volumeName.ToString(); var handle = GetHandle(volume.TrimEnd('\\')); bool returnHandle = false; try { var DiskExtents = new VOLUME_DISK_EXTENTS(); var outBufferSize = (UInt32)Marshal.SizeOf(DiskExtents); var outBuffer = Marshal.AllocHGlobal((int)outBufferSize); if (!DeviceIoControl(handle, IoCtl.VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, outBuffer, outBufferSize, out _, IntPtr.Zero)) { continue; } Marshal.PtrToStructure(outBuffer, DiskExtents); if (DiskExtents.Extents.DiskNumber == 0 || DiskExtents.Extents.DiskNumber != usb.Index) { continue; } if (DiskExtents.Extents.StartingOffset == partitionOffset) { FindVolumeClose(volumeHandle); //Console.WriteLine("FSCTL_ALLOW_EXTENDED_DASD_IO: " + DeviceIoControl(handle, IoCtl.FSCTL_ALLOW_EXTENDED_DASD_IO, // IntPtr.Zero, 0, IntPtr.Zero, 0, out _, IntPtr.Zero)); int i2 = 0; while (!DeviceIoControl(handle, IoCtl.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out _, IntPtr.Zero)) { Thread.Sleep(100); if (i2 > 10) break; i2++; } returnHandle = true; } } finally { if (!returnHandle) handle.Dispose(); } if (returnHandle) return handle; } while (FindNextVolume(volumeHandle, volumeName, 1024)); FindVolumeClose(volumeHandle); } throw new Exception(); } internal static bool VerifyLetterMatches(char letter, USB.UsbDisk usb, long partitionOffset) { using var hLogicalDrive = CreateFile($@"\\.\{letter}:", Win32.FileAccess.GenericRead | Win32.FileAccess.GenericWrite, Win32.FileShare.Read | Win32.FileShare.Write, IntPtr.Zero, Win32.FileMode.OpenExisting, Win32.FileAttributes.Normal, IntPtr.Zero); if (hLogicalDrive.DangerousGetHandle() == INVALID_HANDLE_VALUE) return false; var DiskExtents = new Win32.VOLUME_DISK_EXTENTS(); var outBufferSize = (UInt32)Marshal.SizeOf(DiskExtents); var outBuffer = Marshal.AllocHGlobal((int)outBufferSize); if (!DeviceIoControl(hLogicalDrive, Win32.IoCtl.VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, outBuffer, outBufferSize, out uint returned, IntPtr.Zero)) { return false; } Marshal.PtrToStructure(outBuffer, DiskExtents); if (DiskExtents.NumberOfDiskExtents != 1) return false; if (usb.Index == DiskExtents.Extents.DiskNumber && (partitionOffset == 0 || DiskExtents.Extents.StartingOffset == partitionOffset)) return true; return false; } /* internal static bool VerifyLetterMatches(char letter, USB.UsbDisk usb, long partitionOffset) { char logicalLetter = ' '; foreach (var logicalDrive in System.IO.DriveInfo.GetDrives()) { if ((logicalDrive.DriveType == DriveType.Network || logicalDrive.DriveType == DriveType.Ram || logicalDrive.DriveType == DriveType.CDRom) || (usb.SCSI && !usb.UASP && !usb.VHD && (logicalDrive.DriveType == DriveType.Fixed))) continue; var hLogicalDrive = CreateFile($@"\\.\{logicalDrive.Name.TrimEnd('\\')}", Win32.FileAccess.GenericRead | Win32.FileAccess.GenericWrite, Win32.FileShare.Read | Win32.FileShare.Write, IntPtr.Zero, Win32.FileMode.OpenExisting, Win32.FileAttributes.Normal, IntPtr.Zero); if (hLogicalDrive == INVALID_HANDLE_VALUE) { hLogicalDrive.Dispose(); return false; } var DiskExtents = new Win32.VOLUME_DISK_EXTENTS(); var outBufferSize = (UInt32)Marshal.SizeOf(DiskExtents); var outBuffer = Marshal.AllocHGlobal((int)outBufferSize); if (!DeviceIoControl(hLogicalDrive, Win32.IoCtl.VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, outBuffer, outBufferSize, out uint returned, IntPtr.Zero)) continue; Marshal.PtrToStructure(outBuffer, DiskExtents); if (DiskExtents.NumberOfDiskExtents != 1) continue; if (usb.Index == DiskExtents.Extents.DiskNumber && DiskExtents.Extents.StartingOffset == partitionOffset) { return true; } } if (logicalLetter == ' ') throw new FileNotFoundException("Could not find volume for partition."); return null; } */ internal static void PrepareDrive(USB.UsbDisk usb) { RemoveMounts(usb); using (var handle = Wrap.Retry().Execute(() => GetDriveHandle(usb.Index))) { GetGeometry(handle); GetLayout(handle); } Wrap.HelperRetry().ExecuteSafe(() => Helper.DeletePartitions(usb.Index), true); } internal static DISK_GEOMETRY_EX GetGeometry(SafeFileHandle handle) { // IoCtl.DISK_GET_DRIVE_GEOMETRY_EX to get the physical disk's geometry ( we need some information in it to fill partition data) //The number of surfaces (or heads, which is the same thing), cylinders, and sectors vary a lot; the specification of the number of each is called the geometry of a hard disk. //The geometry is usually stored in a special, battery-powered memory location called the CMOS RAM , from where the operating system can fetch it during bootup or driver initialization. using (var geometryBuffer = new SafeHGlobalHandle(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DISK_GEOMETRY_EX))))) { DISK_GEOMETRY_EX geometry = new DISK_GEOMETRY_EX(); Marshal.StructureToPtr(geometry, geometryBuffer.DangerousGetHandle(), false); DeviceIoControl(handle, IoCtl.DISK_GET_DRIVE_GEOMETRY_EX, IntPtr.Zero, 0, geometryBuffer.DangerousGetHandle(), (uint)Marshal.SizeOf(typeof(DISK_GEOMETRY_EX)), out uint returned, IntPtr.Zero); return (DISK_GEOMETRY_EX)Marshal.PtrToStructure(geometryBuffer.DangerousGetHandle(), typeof(DISK_GEOMETRY_EX)); } } internal static DRIVE_LAYOUT_INFORMATION_EX GetLayout(SafeFileHandle handle) { using (var layoutBuffer = new SafeHGlobalHandle(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DRIVE_LAYOUT_INFORMATION_EX))))) { DRIVE_LAYOUT_INFORMATION_EX layout = new DRIVE_LAYOUT_INFORMATION_EX(); Marshal.StructureToPtr(layout, layoutBuffer.DangerousGetHandle(), false); if (!DeviceIoControl(handle, IoCtl.DISK_GET_DRIVE_LAYOUT_EX, IntPtr.Zero, 0, layoutBuffer.DangerousGetHandle(), (uint)Marshal.SizeOf(typeof(DRIVE_LAYOUT_INFORMATION_EX)), out _, IntPtr.Zero)) { throw new Exception("DISK_GET_DRIVE_LAYOUT_EX Failed"); } return (DRIVE_LAYOUT_INFORMATION_EX)Marshal.PtrToStructure(layoutBuffer.DangerousGetHandle(), typeof(DRIVE_LAYOUT_INFORMATION_EX)); } } internal static void LockLogicalHandles(USB.UsbDisk usb, SafeFileHandle handle, out List logicalHandles) { logicalHandles = new List(); var layout = GetLayout(handle); foreach (var partitionInformationExe in layout.PartitionEntry.Where(x => x.StartingOffset != 0)) { logicalHandles.Add(Wrap.ExecuteSafe(() => { var logicalHandle = GetVolumeHandle(usb, partitionInformationExe.StartingOffset); return logicalHandle; }).Value); } } private const long _efiPartitionSize = 1048576; public const ulong GPT_BASIC_DATA_ATTRIBUTE_NO_DRIVE_LETTER = 0x8000000000000000; public const ulong GPT_BASIC_DATA_ATTRIBUTE_HIDDEN = 0x4000000000000000; public const ulong GPT_BASIC_DATA_ATTRIBUTE_READ_ONLY = 0x1000000000000000; internal static int CreatePartitionGPT(USB.UsbDisk usb, long size, bool dataPartition) { Wrap.Retry().ExecuteSafe(() => PrepareDrive(usb), true); SafeFileHandle handle = Wrap.Retry().Execute(() => GetDriveHandle(usb.Index, Win32.FileAttributes.NoBuffering)); try { // Lock any remaining logical handles in case we // couldn't delete them all with PrepareDrive LockLogicalHandles(usb, handle, out List logicalHandles); var geometry = GetGeometry(handle); //IoCtl.DISK_SET_DRIVE_LAYOUT_EX to repartition a disk as specified. // // Use IoCtl.DISK_UPDATE_PROPERTIES to synchronize system view after IoCtl.DISK_CREATE_DISK and IoCtl.DISK_SET_DRIVE_LAYOUT_EX /* DWORD driveLayoutSize = sizeof(DRIVE_LAYOUT_INFORMATION_EX) + sizeof(PARTITION_INFORMATION_EX) * 4 * 25; DRIVE_LAYOUT_INFORMATION_EX *DriveLayoutEx = (DRIVE_LAYOUT_INFORMATION_EX *) new BYTE[driveLayoutSize]; */ var layoutSize = Marshal.SizeOf(typeof(DRIVE_LAYOUT_INFORMATION_EX)); //var driveLayoutBuffer = Marshal.AllocHGlobal(layoutSize + 4); //FillMemory(driveLayoutBuffer, (uint)layoutSize * 4, 0); DRIVE_LAYOUT_INFORMATION_EX driveLayoutEx = new DRIVE_LAYOUT_INFORMATION_EX(); int pn = 0; driveLayoutEx.PartitionEntry = new PARTITION_INFORMATION_EX[4]; _mediaType = (MEDIA_TYPE)geometry.Geometry.MediaType; Int64 bytes_per_track = (geometry.Geometry.SectorsPerTrack) * (geometry.Geometry.BytesPerSector); driveLayoutEx.PartitionEntry[pn].StartingOffset = bytes_per_track; usb.ISOPartitionOffset = bytes_per_track; Int64 main_part_size_in_sectors, extra_part_size_in_sectors = 0; main_part_size_in_sectors = (geometry.DiskSize.QuadPart - driveLayoutEx.PartitionEntry[pn].StartingOffset) / geometry.Geometry.BytesPerSector; if (main_part_size_in_sectors <= 0) { return -1; } extra_part_size_in_sectors = (MIN_EXTRA_PART_SIZE + bytes_per_track - 1) / bytes_per_track; main_part_size_in_sectors = ((main_part_size_in_sectors / geometry.Geometry.SectorsPerTrack) - extra_part_size_in_sectors) * geometry.Geometry.SectorsPerTrack; if (main_part_size_in_sectors <= 0) { return -1; } var availableExcessSize = geometry.DiskSize.QuadPart - bytes_per_track - bytes_per_track - size - (_efiPartitionSize + bytes_per_track); var excessSize = dataPartition ? Math.Min(size / 8, availableExcessSize) : availableExcessSize; long partlength = (size + (excessSize)); while (partlength % bytes_per_track != 0) { partlength--; } var isoGuid = Guid.NewGuid(); driveLayoutEx.PartitionEntry[pn].PartitionLength = ((partlength)); driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; driveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = new Guid("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"); driveLayoutEx.PartitionEntry[pn].Gpt.Name = "ISO"; driveLayoutEx.PartitionEntry[pn].Gpt.PartitionId = isoGuid; driveLayoutEx.PartitionEntry[pn].PartitionNumber = (uint)pn + 1; driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; driveLayoutEx.PartitionEntry[pn].RewritePartition = true; var dataGuid = Guid.NewGuid(); bool dataPartitionSet = false; var partlength2 = (geometry.DiskSize.QuadPart - bytes_per_track - bytes_per_track - partlength) - (_efiPartitionSize + bytes_per_track); if (dataPartition && partlength2 - bytes_per_track >= 1000000) { dataPartitionSet = true; pn++; // Set the optional extra partition // Should end on a track boundary while (partlength2 % bytes_per_track != 0) { partlength2--; } driveLayoutEx.PartitionEntry[pn].StartingOffset = bytes_per_track + partlength; usb.DataPartitionOffset = bytes_per_track + partlength; driveLayoutEx.PartitionEntry[pn].PartitionLength = partlength2; driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; driveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = new Guid("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"); driveLayoutEx.PartitionEntry[pn].Gpt.Name = "Data"; driveLayoutEx.PartitionEntry[pn].Gpt.PartitionId = dataGuid; driveLayoutEx.PartitionEntry[pn].PartitionNumber = (uint)pn + 1; driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; driveLayoutEx.PartitionEntry[pn].RewritePartition = true; } var efiGuid = Guid.NewGuid(); var efiLength = (_efiPartitionSize + bytes_per_track); if (true) { pn++; // Set the optional extra partition // Should end on a track boundary while (partlength2 % bytes_per_track != 0) { partlength2--; } driveLayoutEx.PartitionEntry[pn].StartingOffset = dataPartitionSet ? bytes_per_track + partlength + partlength2 : bytes_per_track + partlength; usb.EFIPartitionOffset = dataPartitionSet ? bytes_per_track + partlength + partlength2 : bytes_per_track + partlength; driveLayoutEx.PartitionEntry[pn].PartitionLength = efiLength; driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; driveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = new Guid("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"); //driveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = new Guid("C12A7328-F81F-11D2-BA4B-00A0C93EC93B"); driveLayoutEx.PartitionEntry[pn].Gpt.Name = "UEFI:NTFS"; driveLayoutEx.PartitionEntry[pn].Gpt.PartitionId = efiGuid; driveLayoutEx.PartitionEntry[pn].Gpt.Attributes = GPT_BASIC_DATA_ATTRIBUTE_READ_ONLY | GPT_BASIC_DATA_ATTRIBUTE_NO_DRIVE_LETTER; // | GPT_BASIC_DATA_ATTRIBUTE_HIDDEN; HIDDEN causes 24H2+ Windows setup error :( driveLayoutEx.PartitionEntry[pn].PartitionNumber = (uint)pn + 1; driveLayoutEx.PartitionEntry[pn].PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; driveLayoutEx.PartitionEntry[pn].RewritePartition = true; ISO.DD_EFI_ISO(ref handle, usb.Index, usb.EFIPartitionOffset); } driveLayoutEx.PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; driveLayoutEx.PartitionCount = dataPartition ? (uint)3 : 2; //Step 3: IoCtl.DISK_CREATE_DISK is used to initialize a disk with an empty partition table. CREATE_DISK createDisk = new CREATE_DISK(); createDisk.PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; createDisk.Gpt.MaxPartitionCount = 16; var diskGuid = Guid.NewGuid(); createDisk.Gpt.DiskId = diskGuid; // createDisk.PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT; // createDisk.Gpt.DiskId = new Guid("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7"); // createDisk.Gpt.MaxPartitionCount = 1; using (var createDiskBuffer = new SafeHGlobalHandle(Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CREATE_DISK))))) { Marshal.StructureToPtr(createDisk, createDiskBuffer.DangerousGetHandle(), false); FillMemory(createDiskBuffer.DangerousGetHandle(), (uint)Marshal.SizeOf(typeof(CREATE_DISK)), 0); byte[] arr1 = new byte[Marshal.SizeOf(typeof(CREATE_DISK))]; Marshal.Copy(createDiskBuffer.DangerousGetHandle(), arr1, 0, Marshal.SizeOf(typeof(CREATE_DISK))); DeviceIoControl(handle, IoCtl.DISK_CREATE_DISK, createDiskBuffer.DangerousGetHandle(), (uint)Marshal.SizeOf(typeof(CREATE_DISK)), IntPtr.Zero, 0, out _, IntPtr.Zero); } var bytes = new byte[layoutSize]; var layoutHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned); ZeroMemory(layoutHandle.AddrOfPinnedObject(), (uint)layoutSize); Marshal.StructureToPtr(driveLayoutEx, layoutHandle.AddrOfPinnedObject(), false); if (!DeviceIoControl(handle, IoCtl.DISK_SET_DRIVE_LAYOUT_EX, layoutHandle.AddrOfPinnedObject(), 624, IntPtr.Zero, 0, out _, IntPtr.Zero)) { throw new Win32Exception("DISK_SET_DRIVE_LAYOUT_EX Failed"); } layoutHandle.Free(); //Console.WriteLine("FSCTL_UNLOCK_VOLUME: " + DeviceIoControl(handle, IoCtl.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, //0, IntPtr.Zero, (uint)Marshal.SizeOf(typeof(DISK_GEOMETRY_EX)), out returned, IntPtr.Zero)); if (!DeviceIoControl(handle, IoCtl.DISK_UPDATE_PROPERTIES, IntPtr.Zero, 0, IntPtr.Zero, 0, out _, IntPtr.Zero)) { throw new Win32Exception("DISK_SET_DRIVE_LAYOUT_EX Failed"); } handle.Dispose(); logicalHandles.ForEach(x => x.Dispose()); return 1; } finally { handle.Dispose(); } } internal static void Chkdsk(char driveLetter) { ChkdskCallbackDelegate callback = new ChkdskCallbackDelegate(ChkdskCallback); Chkdsk(@$"{driveLetter}:\", "NTFS", false, false, false, false, IntPtr.Zero, IntPtr.Zero, callback); } public static bool ChkdskCallback(FmifsCallbackCommand Command, uint Action, IntPtr pData) { switch (Command) { case FmifsCallbackCommand.Progress: case FmifsCallbackCommand.CheckDiskProgress: int percent = Marshal.ReadInt32(pData); // Read DWORD as int //Log.WriteSafe(LogType.Info, "Chkdsk progress: " + percent, null); break; case FmifsCallbackCommand.Done: bool success = Marshal.ReadByte(pData) != 0; // BOOLEAN as byte if (!success) { Log.WriteSafe(LogType.Warning, "Chkdsk callback failed", null); return false; } break; case FmifsCallbackCommand.Unknown1A: case FmifsCallbackCommand.Output: case FmifsCallbackCommand.DoneWithStructure: break; case FmifsCallbackCommand.NoMediaInDrive: case FmifsCallbackCommand.AccessDenied: case FmifsCallbackCommand.IncompatibleFileSystem: case FmifsCallbackCommand.MediaWriteProtected: case FmifsCallbackCommand.VolumeInUse: Log.WriteSafe(LogType.Warning, "Chkdsk callback failed with result: " + Command, null); return false; case FmifsCallbackCommand.ReadOnlyMode: break; } return true; } [DllImport("fmifs.dll", CharSet = CharSet.Unicode)] public static extern void Chkdsk( string DriveRoot, // Drive path, e.g., "C:\\" string Format, // File system format, e.g., "NTFS" bool FixErrors, // Whether to fix errors bool VigorousIndexCheck, // Thorough index check bool SkipFolderCycle, // Skip folder cycle checking bool ForceDismount, // Force dismount of the volume IntPtr Unused1, IntPtr Unused2, ChkdskCallbackDelegate Callback // Callback function ); public delegate bool ChkdskCallbackDelegate(FmifsCallbackCommand Command, uint Action, IntPtr pData); // Enum for callback commands (partial list) public enum FmifsCallbackCommand { Progress, DoneWithStructure, Unknown2, IncompatibleFileSystem, Unknown4, Unknown5, AccessDenied, MediaWriteProtected, VolumeInUse, CantQuickFormat, UnknownA, Done, BadLabel, UnknownD, Output, StructureProgress, ClusterSizeTooSmall, ClusterSizeTooBig, VolumeTooSmall, VolumeTooBig, NoMediaInDrive, Unknown15, Unknown16, Unknown17, DeviceNotReady, CheckDiskProgress, Unknown1A, Unknown1B, Unknown1C, Unknown1D, Unknown1E, Unknown1F, ReadOnlyMode, Unknown21, Unknown22, Unknown23, Unknown24, AlignmentViolation } internal static void UnmountUEFINTFS() { var drives = DriveInfo.GetDrives() .Where(d => Wrap.ExecuteSafe(() => d.VolumeLabel).Value == "UEFI_NTFS"); foreach (var drive in drives) { string driveLetter = drive.Name.Substring(0, 1); if (!DeleteVolumeMountPoint($@"{driveLetter}:\")) { Log.WriteSafe(LogType.Warning, "DeleteVolumeMountPoint failed during UEFI_NTFS unmount attempt: " + Marshal.GetLastWin32Error(), null); return; } else Log.WriteSafe(LogType.Info, "Unmounted UEFI_NTFS volume on " + $@"{driveLetter}:\", null); } } internal static void RemountSafe(char driveLetter) { StringBuilder volumeName = new StringBuilder(52); if (!GetVolumeNameForVolumeMountPoint($@"{driveLetter}:\", volumeName, (uint)volumeName.Capacity)) { Log.WriteSafe(LogType.Warning, "GetVolumeNameForVolumeMountPoint Failed: " + Marshal.GetLastWin32Error(), null); return; } if (!SetVolumeMountPoint($@"{driveLetter}:\", volumeName.ToString())) { var error = Marshal.GetLastWin32Error(); // 145 = DIR_NOT_EMPTY if (error != 145) { Log.WriteSafe(LogType.Warning, "SetVolumeMountPoint failed during remount attempt: " + error, null); return; } if (!DeleteVolumeMountPoint($@"{driveLetter}:\")) { Log.WriteSafe(LogType.Warning, "DeleteVolumeMountPoint failed during remount attempt: " + error, null); return; } if (!SetVolumeMountPoint($@"{driveLetter}:\", volumeName.ToString())) { Log.WriteSafe(LogType.Warning, "SetVolumeMountPoint failed after delete during remount attempt: " + error, null); return; } } else Log.WriteSafe(LogType.Warning, "Unexpected success of SetVolumeMountPoint when remounting.", null); } internal static void TryFlushDrive(uint driveIndex) { Wrap.ExecuteSafe(() => { using var handle = GetHandle($@"\\.\PhysicalDrive{driveIndex}", Win32.FileAttributes.Normal, Win32.FileShare.Read | Win32.FileShare.Write | Win32.FileShare.Delete); Win32.FlushFileBuffers(handle); }, true); } internal static void TryFlushDrive(char driveLetter) { Wrap.ExecuteSafe(() => { using var handle = GetHandle($@"\\.\{driveLetter}:", Win32.FileAttributes.Normal, Win32.FileShare.Read | Win32.FileShare.Write | Win32.FileShare.Delete); Win32.FlushFileBuffers(handle); }, true); } internal static void FlushDrive(SafeFileHandle handle) => Win32.FlushFileBuffers(handle); internal static SafeFileHandle GetHandle(string path, Win32.FileAttributes attributes = Win32.FileAttributes.Normal, Win32.FileShare share = Win32.FileShare.Read | Win32.FileShare.Write) { var handle = CreateFile(path, Win32.FileAccess.GenericRead | Win32.FileAccess.GenericWrite, share, IntPtr.Zero, Win32.FileMode.OpenExisting, attributes, IntPtr.Zero); if (handle.DangerousGetHandle() == INVALID_HANDLE_VALUE) { handle = CreateFile(path, Win32.FileAccess.GenericRead | Win32.FileAccess.GenericWrite, Win32.FileShare.Read | Win32.FileShare.Write, IntPtr.Zero, Win32.FileMode.OpenExisting, attributes, IntPtr.Zero); if (handle.DangerousGetHandle() == INVALID_HANDLE_VALUE) throw new Win32Exception(Marshal.GetLastWin32Error(), $"Error opening handle to path: {path}"); Log.EnqueueSafe(LogType.Warning, $"Could not open drive handle with exclusive write access: " + Marshal.GetLastWin32Error(), null); } return handle; } internal static SafeFileHandle GetDriveHandle(uint driveIndex, Win32.FileAttributes attributes = Win32.FileAttributes.Normal) => GetHandle($@"\\.\PhysicalDrive{driveIndex}", Win32.FileAttributes.Normal, Win32.FileShare.Read); } } ================================================ FILE: TrustedUninstaller.Shared/USB/Drivers.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Http; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using JetBrains.Annotations; using SharpSevenZip; using System.Linq; using System.Text.Json.Serialization; using Core; using TrustedUninstaller.Shared; namespace iso_mode { public class DriverManager { public class Drivers { public Driver[] Graphics { get; set; } = null!; public Driver[] Network { get; set; } = null!; } public class Driver { public string DisplayName { get; set; } = null!; public string Url { get; set; } = null!; public string FileName { get; set; } = null!; public long FileSize { get; set; } public string SHA256Hash { get; set; } = null!; public string ExecutableName { get; set; } = null!; public string Arguments { get; set; } = null!; public string Version { get; set; } = null!; public string[] ExcludedPaths { get; set; } = null!; public string[] InfFiles { get; set; } = null!; public string[] HardwareIDs { get; set; } = null!; public Dictionary? Headers { get; set; } public bool RebootRequired { get; set; } public string? ScrapeUrl { get; set; } public string? ScrapeRegex { get; set; } } public static async Task HandleDrivers(Action onProgressChanged, IProgress reporter, bool graphics, bool network, bool system, string jsonUrl, string cache, string targetFolder) { if (!Directory.Exists(cache)) Directory.CreateDirectory(cache); if (!Directory.Exists(targetFolder)) Directory.CreateDirectory(targetFolder); var remoteGraphicsDrivers = Array.Empty(); var remoteNetworkDrivers = Array.Empty(); Exception exception = null; try { if (graphics || network) { using var httpClient = new HttpProgressClient(); httpClient.Client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"); var json = await (await httpClient.GetAsync(jsonUrl)).Content.ReadAsStringAsync(); var drivers = (Drivers)JsonSerializer.Deserialize(json, typeof(Drivers))!; File.WriteAllText(Path.Combine(targetFolder, "drivers.json"), json); var graphicsWeight = drivers.Graphics.Sum(x => x.FileSize); var networkWeight = drivers.Graphics.Sum(x => x.FileSize); var totalWeight = (graphics ? graphicsWeight : 0) + (network ? networkWeight : 0) + (system ? (((graphics ? graphicsWeight : 0) + (network ? networkWeight : 0)) / 10) : 0); if (graphics) { remoteGraphicsDrivers = drivers.Graphics; await HandleRemoteDriverList(graphicsProgress => onProgressChanged.Invoke(((graphicsWeight * (graphicsProgress / 100)) / totalWeight) * 100), reporter, drivers.Graphics, httpClient, cache, targetFolder); } if (network) { remoteNetworkDrivers = drivers.Network; await HandleRemoteDriverList( networkProgress => onProgressChanged.Invoke((((networkWeight * (networkProgress / 100)) / totalWeight) * 100) + (graphics ? ((double)graphicsWeight / totalWeight) * 100 : 0)), reporter, drivers.Network, httpClient, cache, targetFolder); } } } catch (Exception e) { exception = e; } if (system) { reporter.Report("Copying PC Drivers"); var systemDrivers = GetDriverPathsFromOEMInfs(); //.Where(x => //!remoteGraphicsDrivers.Any(y => y.InfFiles.Any(z => Path.GetFileName(x).StartsWith(z, StringComparison.OrdinalIgnoreCase))) && //!remoteNetworkDrivers.Any(y => y.InfFiles.Any(z => Path.GetFileName(x).StartsWith(z, StringComparison.OrdinalIgnoreCase)))).ToArray(); var totalWeight = systemDrivers.Length; if (!Directory.Exists(Path.Combine(targetFolder, "UserDrivers"))) Directory.CreateDirectory(Path.Combine(targetFolder, "UserDrivers")); foreach (string systemDriver in systemDrivers) { CopyDirectory(systemDriver, Path.Combine(targetFolder, "UserDrivers", Path.GetFileName(systemDriver))); onProgressChanged.Invoke(graphics || network ? ((((Array.IndexOf(systemDrivers, systemDriver) / (double)totalWeight) * (1 - (90d / 100d))) * 100) + 90) : (Array.IndexOf(systemDrivers, systemDriver) / (double)totalWeight) * 100); } } File.Create(Path.Combine(targetFolder, "pending.bool")).Dispose(); if (exception != null) throw exception; } private static async Task HandleRemoteDriverList(Action onProgressChanged, IProgress reporter, Driver[] drivers, HttpProgressClient httpClient, string cache, string targetFolder) { var totalWeight = drivers.Sum(x => x.FileSize); var currentWeight = 0L; for (var i = 0; i < drivers.Length; i++) { try { if (i <= 0 || drivers[i - 1].DisplayName != drivers[i].DisplayName) { reporter.Report($"Downloading {drivers[i].DisplayName + (i != drivers.Length - 1 && drivers[i + 1].DisplayName == drivers[i].DisplayName ? "s" : null)}"); } var destination = Path.Combine(cache, drivers[i].FileName); var extractDestination = Path.Combine(targetFolder, Path.GetFileNameWithoutExtension(destination)); if (drivers[i].Headers != null) { drivers[i].Headers!.ToList().ForEach(x => httpClient.Client.DefaultRequestHeaders.Add(x.Key, x.Value)); } if (File.Exists(destination)) { if (GetSHA256(destination) == drivers[i].SHA256Hash) { // Already downloaded } else { if (Directory.Exists(extractDestination)) Directory.Delete(extractDestination, true); httpClient.ProgressChanged += (size, downloaded, percentage) => { // ReSharper disable once AccessToModifiedClosure onProgressChanged.Invoke(((currentWeight + downloaded) / (double)totalWeight) * 100); }; await httpClient.StartDownload(drivers[i].Url, destination, drivers[i].FileSize); } } else { await httpClient.StartDownload(drivers[i].Url, destination); if (GetSHA256(destination) != drivers[i].SHA256Hash) throw new Exception("Hash mismatch!"); } if (drivers[i].Headers != null) { drivers[i].Headers!.ToList().ForEach(x => httpClient.Client.DefaultRequestHeaders.Remove(x.Key)); } if (destination.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { File.Copy(destination, Path.Combine(targetFolder, Path.GetFileName(destination))); } else if (!Directory.Exists(extractDestination)) { if (TryParseEnum(Path.GetExtension(destination).TrimStart('.'), out InArchiveFormat archiveFormat)) ExtractArchive(destination, extractDestination, (InArchiveFormat)archiveFormat); else throw new Exception("Unknown file type"); foreach (string excludedPath in drivers[i].ExcludedPaths ?? Array.Empty()) { if (File.Exists(Path.Combine(extractDestination, excludedPath))) File.Delete(Path.Combine(extractDestination, excludedPath)); else if (Directory.Exists(Path.Combine(extractDestination, excludedPath))) Directory.Delete(Path.Combine(extractDestination, excludedPath), true); } } } catch (Exception e) { Log.EnqueueExceptionSafe(e); } currentWeight += drivers[i].FileSize; onProgressChanged.Invoke((currentWeight / (double)totalWeight) * 100); } } public static bool TryParseEnum(string value, out TEnum result) where TEnum : struct { result = default; if (string.IsNullOrEmpty(value)) return false; try { result = (TEnum)Enum.Parse(typeof(TEnum), value, true); return true; } catch { return false; } } private static void ExtractArchive(string filePath, string extractDestination, InArchiveFormat format) { SharpSevenZipBase.SetLibraryPath(Path.Combine(Directory.GetCurrentDirectory(), "7z.dll")); using var extractor = new SharpSevenZipExtractor(filePath, format); extractor.ExtractArchive(extractDestination); } static void CopyDirectory(string sourceDir, string destinationDir) { var dir = new DirectoryInfo(sourceDir); if (!dir.Exists) throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); DirectoryInfo[] dirs = dir.GetDirectories(); Directory.CreateDirectory(destinationDir); foreach (FileInfo file in dir.GetFiles()) { string targetFilePath = Path.Combine(destinationDir, file.Name); file.CopyTo(targetFilePath); } foreach (DirectoryInfo subDir in dirs) { string newDestinationDir = Path.Combine(destinationDir, subDir.Name); CopyDirectory(subDir.FullName, newDestinationDir); } } private static string GetSHA256(string filePath) { using var sha256 = SHA256.Create(); using var fileStream = File.OpenRead(filePath); var fileHash = sha256.ComputeHash(fileStream); return BitConverter.ToString(fileHash).Replace("-", "").ToUpperInvariant(); } const int DI_FLAGSEX_INSTALLEDDRIVER = 0x04000000; public static string[] GetDriverPathsFromInstalled() { var paths = new List(); IntPtr devices = Win32.SetupDiGetClassDevs(IntPtr.Zero, null, IntPtr.Zero, (uint)(Win32.DiGetClassFlags.DIGCF_ALLCLASSES)); if (devices == Win32.INVALID_HANDLE_VALUE) throw new Exception("SetupDiGetClassDevs failed with error: " + Marshal.GetLastWin32Error()); Win32.SP_DEVINFO_DATA deviceInfoData = new Win32.SP_DEVINFO_DATA { cbSize = (uint)Marshal.SizeOf(typeof(Win32.SP_DEVINFO_DATA)) }; for (uint i = 0; Win32.SetupDiEnumDeviceInfo(devices, i, ref deviceInfoData); i++) { var installParams = new Win32.SP_DEVINSTALL_PARAMS(); installParams.Initialize(); if (!Win32.SetupDiGetDeviceInstallParams(devices, ref deviceInfoData, ref installParams)) continue; installParams.FlagsEx |= DI_FLAGSEX_INSTALLEDDRIVER; if (!Win32.SetupDiSetDeviceInstallParams(devices, ref deviceInfoData, ref installParams)) continue; if (Win32.SetupDiBuildDriverInfoList(devices, ref deviceInfoData, Win32.SPDIT.COMPATDRIVER)) { var driverInfoData = new Win32.SP_DRVINFO_DATA { cbSize = Marshal.SizeOf(typeof(Win32.SP_DRVINFO_DATA)) }; Win32.SetupDiEnumDriverInfo(devices, ref deviceInfoData, Win32.SPDIT.COMPATDRIVER, 0, ref driverInfoData); for (int j = 0; Win32.SetupDiEnumDriverInfo(devices, ref deviceInfoData, Win32.SPDIT.COMPATDRIVER, (int)j, ref driverInfoData); j++) { Win32.SP_DRVINFO_DETAIL_DATA driverInfoDetailData = new Win32.SP_DRVINFO_DETAIL_DATA { cbSize = Marshal.SizeOf(typeof(Win32.SP_DRVINFO_DETAIL_DATA)) }; if (driverInfoData.ProviderName == "Microsoft") continue; if (Win32.SetupDiGetDriverInfoDetail(devices, ref deviceInfoData, ref driverInfoData, ref driverInfoDetailData, Marshal.SizeOf(driverInfoDetailData), out _) || Marshal.GetLastWin32Error() == 122) { if (!string.IsNullOrWhiteSpace(driverInfoDetailData.InfFileName)) { if (!Win32.SetupGetInfDriverStoreLocation(driverInfoDetailData.InfFileName, 0, 0, null!, 0, out int RequiredSize)) { StringBuilder returnBuffer = new StringBuilder(RequiredSize); if (Win32.SetupGetInfDriverStoreLocation(driverInfoDetailData.InfFileName, 0, 0, returnBuffer, returnBuffer.Capacity, out RequiredSize)) { var storeInf = returnBuffer.ToString(); if (storeInf.IndexOf(@":\WINDOWS\System32\DriverStore\FileRepository\", StringComparison.OrdinalIgnoreCase) >= 0) { var storeDir = Path.Combine(String.Join("\\", storeInf.Split('\\').Take(6))); paths.Add(storeDir); } } } } } } } } Win32.SetupDiDestroyDeviceInfoList(devices); return paths.Distinct().ToArray(); } public static string[] GetDriverPathsFromGlobal() { var paths = new List(); IntPtr devices = Win32.SetupDiCreateDeviceInfoList(IntPtr.Zero, IntPtr.Zero); if (devices == Win32.INVALID_HANDLE_VALUE) throw new Exception("SetupDiCreateDeviceInfoList failed with error: " + Marshal.GetLastWin32Error()); var deviceInfoData = IntPtr.Zero; if (Win32.SetupDiBuildDriverInfoList(devices, deviceInfoData, Win32.SPDIT.CLASSDRIVER)) { var driverInfoData = new Win32.SP_DRVINFO_DATA { cbSize = Marshal.SizeOf(typeof(Win32.SP_DRVINFO_DATA)) }; for (int j = 0; Win32.SetupDiEnumDriverInfo(devices, deviceInfoData, Win32.SPDIT.CLASSDRIVER, (int)j, ref driverInfoData); j++) { Win32.SP_DRVINFO_DETAIL_DATA driverInfoDetailData = new Win32.SP_DRVINFO_DETAIL_DATA { cbSize = Marshal.SizeOf(typeof(Win32.SP_DRVINFO_DETAIL_DATA)) }; if (driverInfoData.ProviderName == "Microsoft") continue; if (Win32.SetupDiGetDriverInfoDetail(devices, deviceInfoData, ref driverInfoData, ref driverInfoDetailData, Marshal.SizeOf(driverInfoDetailData), out _) || Marshal.GetLastWin32Error() == 122) { if (!string.IsNullOrWhiteSpace(driverInfoDetailData.InfFileName) && driverInfoDetailData.InfFileName.Contains(@"INF\oem")) { if (!Win32.SetupGetInfDriverStoreLocation(driverInfoDetailData.InfFileName, 0, 0, null!, 0, out int RequiredSize)) { StringBuilder returnBuffer = new StringBuilder(RequiredSize); if (Win32.SetupGetInfDriverStoreLocation(driverInfoDetailData.InfFileName, 0, 0, returnBuffer, returnBuffer.Capacity, out RequiredSize)) { var storeInf = returnBuffer.ToString(); if (storeInf.IndexOf(@":\WINDOWS\System32\DriverStore\FileRepository\", StringComparison.OrdinalIgnoreCase) >= 0) { var storeDir = Path.Combine(String.Join("\\", storeInf.Split('\\').Take(6))); paths.Add(storeDir); } } } } } } } Win32.SetupDiDestroyDeviceInfoList(devices); return paths.Distinct().ToArray(); } public static string[] GetDriverPathsFromOEMInfs() { var paths = new List(); foreach (string file in Directory.GetFiles(Path.Combine(Path.GetPathRoot(Environment.SystemDirectory)!, @"Windows\INF"), "*.inf")) { if (!Path.GetFileName(file).StartsWith("oem", StringComparison.OrdinalIgnoreCase)) continue; if (!Win32.SetupGetInfDriverStoreLocation(file, 0, 0, null!, 0, out int RequiredSize)) { StringBuilder returnBuffer = new StringBuilder(RequiredSize); if (Win32.SetupGetInfDriverStoreLocation(file, 0, 0, returnBuffer, returnBuffer.Capacity, out RequiredSize)) { var storeInf = returnBuffer.ToString(); if (storeInf.IndexOf(@":\WINDOWS\System32\DriverStore\FileRepository\", StringComparison.OrdinalIgnoreCase) >= 0) { var storeDir = Path.Combine(String.Join("\\", storeInf.Split('\\').Take(6))); paths.Add(storeDir); } } } } return paths.Distinct().ToArray(); } 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.FromMinutes(1) }; } public async Task StartDownload(string downloadUrl, string destinationFilePath, long? size = null) { try { _downloadUrl = downloadUrl; _destinationFilePath = destinationFilePath; using (var response = await Client.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) await DownloadFileFromHttpResponseMessage(response, size); } finally { ProgressChanged = null; } } public Task GetAsync(string link) { return Client.GetAsync(link); } private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response, long? size) { response.EnsureSuccessStatusCode(); if (response.Content.Headers.ContentLength.HasValue && response.Content.Headers.ContentLength.Value != 0) 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(); } } } } ================================================ FILE: TrustedUninstaller.Shared/USB/Format.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Windows.Documents; using Core; using Microsoft.Win32; using static iso_mode.Win32; namespace iso_mode { public class Format { private enum CallbackCommand { PROGRESS, DONEWITHSTRUCTURE, UNKNOWN2, UNKNOWN3, UNKNOWN4, UNKNOWN5, INSUFFICIENTRIGHTS, UNKNOWN7, DISKLOCKEDFORACCESS, UNKNOWN9, UNKNOWNA, DONE, UNKNOWNC, UNKNOWND, OUTPUT, STRUCTUREPROGRESS } private const int FMIFS_HARDDISK = 0xC; private delegate Int32 FormatCallBackDelegate(CallbackCommand callBackCommand, int subActionCommand, IntPtr action); [DllImport("fmifs.dll", EntryPoint = "FormatEx", CharSet = CharSet.Auto, SetLastError = true)] private static extern void FormatEx(string drivePath, int mediaFlag, string fsType, string label, int quickFormat, uint clusterSize, FormatCallBackDelegate callBackDelegate); public static void FormatDrive(string guid, string label, char driveLetter) { string deleteResult; if ((deleteResult = Helper.FormatVolume(driveLetter.ToString(), "NTFS", 0, label)) != "Success") throw new Exception(deleteResult); return; FormatEx(driveLetter + @":\", 0, "NTFS", label, 1, 0, FormatCallback); while (status == FormatStatus.Formatting) { Thread.Sleep(200); } if (status == FormatStatus.Failed) { Console.WriteLine("FAILED!!!: " + driveLetter); throw new Exception("Format failed."); } } private enum FormatStatus { Completed, Failed, Formatting, } private static FormatStatus status = FormatStatus.Formatting; private static Int32 FormatCallback(CallbackCommand callBackCommand, int subActionCommand, IntPtr action) { Console.WriteLine(callBackCommand.ToString()); switch (callBackCommand) { case CallbackCommand.PROGRESS: int percent = Marshal.ReadInt32(action); Console.WriteLine(percent); break; case CallbackCommand.OUTPUT: string output = Marshal.PtrToStringAuto(action); Console.WriteLine(output); break; case CallbackCommand.DONE: bool result = Convert.ToBoolean(Marshal.ReadByte(action)); // if False => Error Console.WriteLine(result + " OOF "); status = result ? FormatStatus.Completed : FormatStatus.Failed; break; } return 1; } } } ================================================ FILE: TrustedUninstaller.Shared/USB/ISO.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Core; using DiscUtils; using DiscUtils.Compression; using DiscUtils.Iso9660; using DiscUtils.Streams; using DiscUtils.Udf; using DiscUtils.Vfs; using Interprocess; using JetBrains.Annotations; using Microsoft.Win32.SafeHandles; using Polly; using Polly.Retry; using static iso_mode.Win32; using FileAccess = System.IO.FileAccess; using FileMode = System.IO.FileMode; using FileShare = System.IO.FileShare; namespace iso_mode { public static class ISO { private static Stream GetFileReadStream(FileStream fileStream) { Stream readStream; try { var headerBytes = new byte[4]; fileStream.Read(headerBytes, 0, 4); fileStream.Seek(0, SeekOrigin.Begin); if (BitConverter.ToUInt16(headerBytes, 0) == 0x8b1f) { byte[] fileSizeBytes = new byte[4]; fileStream.Seek(-4, SeekOrigin.End); fileStream.Read(fileSizeBytes, 0, 4); fileStream.Seek(0, SeekOrigin.Begin); readStream = new GZipStream(fileStream, CompressionMode.Decompress, true); int firstByte = -1; try { firstByte = readStream.ReadByte(); } catch { } readStream.Dispose(); fileStream.Seek(0, SeekOrigin.Begin); if (firstByte == -1) { throw new FileFormatException(); } else { readStream = new GZipStream(fileStream, CompressionMode.Decompress, true); } } else throw new FileFormatException(); } catch (Exception e) { try { readStream = new BZip2.BZip2InputStream(fileStream) { IsStreamOwner = false }; int firstByte = -1; try { firstByte = readStream.ReadByte(); } catch { } readStream.Dispose(); fileStream.Seek(0, SeekOrigin.Begin); if (firstByte == -1) { throw new FileFormatException(); } else { readStream = new BZip2.BZip2InputStream(fileStream) { IsStreamOwner = false }; } } catch (Exception ex) { fileStream.Seek(0, SeekOrigin.Begin); readStream = fileStream; } } return readStream; } [SuppressMessage("ReSharper", "AccessToModifiedClosure")] [SuppressMessage("ReSharper", "AccessToDisposedClosure")] internal static void DD_ISO(USB.UsbDisk usb, string filePath, InterLink.InterProgress interProgress) { using var fileStream = new FileStream(filePath, System.IO.FileMode.Open, FileAccess.Read, FileShare.Read); Wrap.Retry().ExecuteSafe(() => { Drive.PrepareDrive(usb); }, true); using SafeFileHandle targetHandle = Wrap.Retry().Execute(() => Drive.GetDriveHandle(usb.Index, Win32.FileAttributes.NoBuffering)); // Lock any remaining logical handles in case we // couldn't delete them all with PrepareDrive Drive.LockLogicalHandles(usb, targetHandle, out List logicalHandles); var geometry = Drive.GetGeometry(targetHandle); uint chunkSize = (((1024 * 1024) + geometry.Geometry.BytesPerSector - 1) / geometry.Geometry.BytesPerSector) * geometry.Geometry.BytesPerSector; var buffer = new byte[chunkSize]; bool useSecondBuffer = false; var secondBuffer = new byte[chunkSize]; using (var targetStream = new FileStream(targetHandle, FileAccess.ReadWrite)) { Stream readStream = null; Task writeTask = Task.CompletedTask; long offset = 0; long totalRead = 0; double progress = 0; ResiliencePipeline readPipeline = new ResiliencePipelineBuilder() .AddRetry(new RetryStrategyOptions() { MaxRetryAttempts = 2, Delay = TimeSpan.FromMilliseconds(2000), OnRetry = args => { Log.WriteExceptionSafe(args.Outcome.Exception); if (!(readStream is FileStream)) readStream!.Dispose(); if (args.AttemptNumber == 0 && readStream is FileStream) { if (!Wrap.ExecuteSafe(() => fileStream.Seek(totalRead, SeekOrigin.Begin)).Failed) return default; } fileStream.Seek(0, SeekOrigin.Begin); readStream = GetFileReadStream(fileStream); long retryRead = 0; while (retryRead < totalRead) { retryRead += readStream.Read(useSecondBuffer ? secondBuffer : buffer, 0, useSecondBuffer ? secondBuffer.Length : buffer.Length); } return default; }, }) .Build(); long currentChunkSize = chunkSize; readStream = GetFileReadStream(fileStream); try { int amountRead; while ((amountRead = readPipeline.Execute(() => readStream.Read(useSecondBuffer ? secondBuffer : buffer, 0, useSecondBuffer ? secondBuffer.Length : buffer.Length))) > 0) { totalRead += amountRead; Wrap.Retry().Execute(() => { try { try { writeTask.Wait(); } catch (AggregateException exception) { Log.EnqueueExceptionSafe(exception); throw; } } catch (Exception e) { targetStream.Seek(Math.Max(offset - currentChunkSize, 0), SeekOrigin.Begin); writeTask = targetStream.WriteAsync(!useSecondBuffer ? secondBuffer : buffer, 0, !useSecondBuffer ? secondBuffer.Length : buffer.Length); throw; } }); currentChunkSize = chunkSize; if (amountRead != buffer.Length) { currentChunkSize = amountRead; if (currentChunkSize % geometry.Geometry.BytesPerSector != 0) { currentChunkSize = ((currentChunkSize + geometry.Geometry.BytesPerSector - 1) / geometry.Geometry.BytesPerSector) * geometry.Geometry.BytesPerSector; } if (useSecondBuffer) secondBuffer = new byte[currentChunkSize]; else buffer = new byte[currentChunkSize]; } Wrap.Retry().Execute(() => { targetStream.Seek(offset, SeekOrigin.Begin); writeTask = targetStream.WriteAsync(useSecondBuffer ? secondBuffer : buffer, 0, useSecondBuffer ? secondBuffer.Length : buffer.Length); }); var currentProgress = Math.Round((fileStream.Position / (double)fileStream.Length) * 100, 1); if (currentProgress > progress) { progress = currentProgress; interProgress.Report((decimal)Math.Min(10 + (progress * 0.88), 98)); } offset += currentChunkSize; useSecondBuffer = !useSecondBuffer; } } finally { readStream.Dispose(); } } FlushFileBuffers(targetHandle); // ReSharper disable once DisposeOnUsingVariable targetHandle.Dispose(); logicalHandles.ForEach(x => { FlushFileBuffers(x); x.Dispose(); }); } [DllImport("kernel32.dll")] static extern bool SetFilePointerEx(SafeFileHandle hFile, long liDistanceToMove, out long lpNewFilePointer, uint dwMoveMethod); internal static void DD_EFI_ISO(ref SafeFileHandle handle, uint driveIndex, long efiOffset) { SetFilePointerEx(handle, efiOffset, out long returnOffset, 0); using (UnmanagedMemoryStream stream = (UnmanagedMemoryStream)Assembly.GetExecutingAssembly() .GetManifestResourceStream("TrustedUninstaller.Shared.Properties.uefi-ntfs-ame.img")) { var buffer = new byte[stream.Length]; stream.Read(buffer, 0, (int)stream.Length); string error = null; for (int i = 0; i < 10; i++) { if (i != 0) { handle.Dispose(); handle = Drive.GetDriveHandle(driveIndex); } bool success = WriteFile(handle, buffer, (uint)stream.Length, out uint bytesWritten, IntPtr.Zero); if (success) { error = null; FlushFileBuffers(handle); break; } error = Marshal.GetLastWin32Error().ToString(); Thread.Sleep(500); } if (error != null) Log.WriteSafe(LogType.Error, "Could not write EFI image: " + error, null); } } internal static async Task WriteISO(string filePath, string path, [CanBeNull] IProgress mainProgress) { var size = new FileInfo(filePath).Length; using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { VfsFileSystemFacade reader; try { reader = new UdfReader(fileStream); } catch (Exception e) { reader = new CDReader(fileStream, true); if (reader.GetFileSystemEntries(@"\" + reader.Root.Name).All(x => x.TrimStart('\\').Equals("README.TXT", StringComparison.OrdinalIgnoreCase))) { reader.Dispose(); throw new Exception("UDF open failed and CDReader README found."); } } var progress = new Progress(x => { if (mainProgress != null) mainProgress.Report(Math.Min(10 + (((decimal)(x * 100) / size) * (decimal)0.88), 98)); }); await Task.Run(() => { ExtractISODirectory(reader.Root, path, "", progress, 0, 0); }); reader.Dispose(); mainProgress?.Report(100); } } private static (long PendingBytes, long TotalBytes) ExtractISODirectory(DiscDirectoryInfo dirInfo, string rootPath, string pathInISO, IProgress progress, long pendingBytes, long totalBytes) { if (!string.IsNullOrWhiteSpace(pathInISO)) { pathInISO += "\\" + dirInfo.Name; } rootPath += "\\" + dirInfo.Name; if (!Directory.Exists(rootPath)) { Wrap.RetryExponential.Execute(() => Directory.CreateDirectory(rootPath)); } foreach (DiscDirectoryInfo subDirInfo in dirInfo.GetDirectories()) { var byteInfo = ExtractISODirectory(subDirInfo, rootPath, pathInISO, progress, pendingBytes, totalBytes); pendingBytes = byteInfo.PendingBytes; totalBytes = byteInfo.TotalBytes; } foreach (DiscFileInfo fileInfo in dirInfo.GetFiles()) { Wrap.RetryExponential.Execute(() => { using (Stream fileStream = fileInfo.OpenRead()) { using (FileStream destination = File.Create(rootPath + "\\" + fileInfo.Name)) { var buffer = new byte[4096]; int bytesRead; var tenMb = 10L * 1024 * 1024; while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0) { destination.Write(buffer, 0, bytesRead); pendingBytes += bytesRead; if (pendingBytes > tenMb) { totalBytes += pendingBytes; pendingBytes = 0; progress.Report(totalBytes); } } } } }); } return (pendingBytes, totalBytes); } } } ================================================ FILE: TrustedUninstaller.Shared/USB/ISOWIM.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Core; using ManagedWimLib; using Microsoft.Win32; using TrustedUninstaller.Shared; using TrustedUninstaller.Shared.Actions; namespace iso_mode { public static class ISOWIM { public static void SetBootWimRequirements(string isoPath, bool? tpm, bool? cpuRam) { var guid = Guid.NewGuid().ToString().Replace("-", "").Replace("{", "").Replace("}", ""); try { Wim.GlobalInit("libwim-15.dll", InitFlags.None); } catch (InvalidOperationException e) { // Already initialized } if (File.Exists(Path.Combine(isoPath, @"sources\boot.wim"))) { using var bootWim = Wim.OpenWim(Path.Combine(isoPath, @"sources\boot.wim"), OpenFlags.None); int image = bootWim.GetWimInfo().BootIndex == 0 ? 1 : (int)bootWim.GetWimInfo().BootIndex; var systemHivePath = Path.Combine(Path.GetTempPath(), "AME-BOOTWIM-" + guid, "SYSTEM"); bootWim.ExtractPath(image, Path.GetDirectoryName(systemHivePath), @"Windows\System32\config\SYSTEM", ExtractFlags.NoPreserveDirStructure); if (Wrap.ExecuteSafe(() => WinUtil.RegistryManager.HookHive("BOOT-" + guid, systemHivePath), true) == null) { using (var labKey = Registry.Users.CreateSubKey("BOOT-" + guid + @"\Setup\LabConfig")!) { if (cpuRam.HasValue) { labKey.SetValue("BypassRAMCheck", cpuRam == true ? 1 : 0, RegistryValueKind.DWord); labKey.SetValue("BypassSecureBootCheck", cpuRam == true ? 1 : 0, RegistryValueKind.DWord); labKey.SetValue("BypassCPUCheck", cpuRam == true ? 1 : 0, RegistryValueKind.DWord); } if (tpm.HasValue) labKey.SetValue("BypassTPMCheck", tpm == true ? 1 : 0, RegistryValueKind.DWord); } if (Wrap.ExecuteSafe(() => WinUtil.RegistryManager.UnhookHive("BOOT-" + guid), true) == null) { bootWim.UpdateImage( image, UpdateCommand.SetAdd(systemHivePath, @"Windows\System32\config\SYSTEM", null, AddFlags.None), UpdateFlags.None); bootWim.Overwrite(WriteFlags.None, Wim.DefaultThreads); } } Wrap.ExecuteSafe(() => Directory.Delete(Path.GetDirectoryName(systemHivePath)!, true), true); } } public static async Task SetInstallWimRequirements(string isoPath, string isoArch, bool? internet, bool? bitlocker) { Directory.CreateDirectory(Path.Combine(isoPath, @"sources\$OEM$\$$\Panther")); var unattendPath = Path.Combine(isoPath, @"sources\$OEM$\$$\Panther\unattend.xml"); bool bitlockerSet = false; bool internetSet = false; if (File.Exists(unattendPath)) { Wrap.ExecuteSafe(() => { var text = File.ReadAllText(unattendPath); if (text.Contains("true")) bitlockerSet = true; if (text.Contains("BypassNRO /t REG_DWORD /d 1")) internetSet = true; }, true); } if ((internet == false || (!internet.HasValue && !internetSet)) && (bitlocker == false || (!bitlocker.HasValue && !bitlockerSet))) { if (File.Exists(unattendPath)) File.Delete(unattendPath); } else File.WriteAllText(unattendPath, GenerateUnattendXml(isoArch, internet ?? internetSet, bitlocker ?? bitlockerSet)); return; var guid = Guid.NewGuid().ToString().Replace("-", "").Replace("{", "").Replace("}", ""); using var wrapper = WimWrapper.OpenWim(Path.Combine(Path.Combine(isoPath, @"sources\install.wim"))); try { wrapper.MountHives(guid); try { AmeliorationUtil.ISO = true; AmeliorationUtil.ISOGuid = guid; await new RegistryValueAction() { KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\BitLocker", Value = "PreventDeviceEncryption", Data = 1, Type = RegistryValueType.REG_DWORD, }.RunTask(Output.OutputWriter.Null); await new RegistryValueAction() { KeyName = @"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE", Value = "BypassNRO", Type = RegistryValueType.REG_DWORD, }.RunTask(Output.OutputWriter.Null); } finally { AmeliorationUtil.ISO = false; AmeliorationUtil.ISOGuid = null; } } finally { wrapper.UnmountHives(guid); } } public static void ConvertWIMToESD(string isoPath) { if (File.Exists(Path.Combine(isoPath, @"sources\install.esd"))) return; if (!File.Exists(Path.Combine(isoPath, @"sources\install.wim"))) { Log.EnqueueSafe(LogType.Warning, "ISO install.wim not found.", new SerializableTrace()); return; } using (var wrapper = WimWrapper.OpenWim(Path.Combine(Path.Combine(isoPath, @"sources\install.wim")))) { wrapper.WriteToESD(Path.Combine(isoPath, @"sources\install.esd")); } File.Delete(Path.Combine(isoPath, @"sources\install.wim")); } public static string GenerateUnattendXml( string arch, bool noOnlineAccount, bool disableBitlocker) { var xml = new StringBuilder(); xml.AppendLine(""); xml.AppendLine(""); string componentAttributes = $"processorArchitecture=\"{arch}\" language=\"neutral\" " + "xmlns:wcm=\"http://schemas.microsoft.com/WMIConfig/2002/State\" " + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + "publicKeyToken=\"31bf3856ad364e35\" versionScope=\"nonSxS\""; if (noOnlineAccount) { xml.AppendLine(" "); xml.AppendLine($" "); xml.AppendLine(" "); xml.AppendLine(" "); xml.AppendLine(" 1"); xml.AppendLine(" reg add HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE /v BypassNRO /t REG_DWORD /d 1 /f"); xml.AppendLine(" "); xml.AppendLine(" "); xml.AppendLine(" "); xml.AppendLine(" "); } if (disableBitlocker) { xml.AppendLine(" "); xml.AppendLine($" "); xml.AppendLine(" true"); xml.AppendLine(" "); xml.AppendLine($" "); xml.AppendLine(" 1"); xml.AppendLine(" "); xml.AppendLine(" "); } xml.AppendLine(""); return xml.ToString(); } } } ================================================ FILE: TrustedUninstaller.Shared/USB/InterMethods.cs ================================================ using System; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Core; using Interprocess; using ManagedWimLib; namespace iso_mode { public class InterMethods { [InterprocessMethod(Level.Administrator)] public static async Task WriteISO(string isoArch, bool windows, string deviceId, uint diskIndex, string isoPath, string label, bool? tpm, bool? cpuRam, bool? internet, bool? bitlocker, InterLink.InterProgress progress) { progress.Report(1); var usb = Wrap.Retry().Execute(() => { // TODO: Change log value var usb = Wrap.RetryExponential.Execute(() => USB.GetDevices(true, true).FirstOrDefault(x => x.UsbDeviceID == deviceId && x.Index == diskIndex)); if (usb == null) { throw new Exception("Device not found"); } return usb; }); progress.Report(2); if (windows) { var size = new FileInfo(isoPath).Length; Wrap.RetryExponential.Execute(() => { if (Drive.CreatePartitionGPT(usb, size, false) != 1) { throw new Exception("Failed to create GPT partition table."); } }); progress.Report(3); var mount = Wrap.RetryExponential.Execute(() => Drive.GetVolumeMount(usb, usb.ISOPartitionOffset, true, true)); progress.Report(5); Wrap.RetryExponential.Execute(() => Format.FormatDrive(mount.VolumeName.Substring(mount.VolumeName.IndexOf('{') + 1, mount.VolumeName.IndexOf('}') - (mount.VolumeName.IndexOf('{') + 1)), label, mount.Letter!.Value!)); Wrap.RetryExponential.ExecuteSafe(Drive.UnmountUEFINTFS, true); progress.Report(10); await ISO.WriteISO(isoPath, mount.Letter!.Value + @":\", progress); Wrap.ExecuteSafe(() => { using (var handle = Wrap.RetryExponential.Execute(() => Drive.GetHandle(@$"\\.\{mount.Letter.Value}:"))) { if (!Win32.FlushFileBuffers(handle)) throw new Win32Exception(Marshal.GetLastWin32Error()); } }, true); Drive.TryFlushDrive(mount.Letter.Value); Drive.RemountSafe(mount.Letter.Value); if (File.Exists(mount.Letter.Value + @":\sources\boot.wim")) { if (tpm.HasValue || cpuRam.HasValue) Wrap.ExecuteSafe(() => ISOWIM.SetBootWimRequirements(mount.Letter.Value + @":\", tpm, cpuRam), true); if (internet.HasValue || bitlocker.HasValue) Wrap.ExecuteSafe(() => ISOWIM.SetInstallWimRequirements(mount.Letter.Value + @":\", isoArch, internet, bitlocker), true); Drive.RemountSafe(mount.Letter.Value); } Wrap.ExecuteSafe(() => Drive.Chkdsk(mount.Letter.Value), true); Wrap.RetryExponential.ExecuteSafe(Drive.UnmountUEFINTFS, true); if (usb.UsbDeviceID != null) { using (var handle = Wrap.RetryExponential.Execute(() => Drive.GetHandle(@$"\\.\{mount.Letter.Value}:"))) { if (!Win32.DeviceIoControl(handle, Win32.IoCtl.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out _, IntPtr.Zero)) { if (Wrap.ExecuteSafe(() => USB.Eject(usb.UsbDeviceID), false) == null) Log.WriteSafe(LogType.Info, "Successfully ejected USB.", null); } else Win32.DeviceIoControl(handle, Win32.IoCtl.FSCTL_UNLOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out _, IntPtr.Zero); } } } else { Wrap.Retry().Execute(() => ISO.DD_ISO(usb, isoPath, progress)); } Drive.TryFlushDrive(usb.Index); } } } ================================================ FILE: TrustedUninstaller.Shared/USB/Interop.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Text; using Microsoft.Win32.SafeHandles; namespace iso_mode { internal static class Helper { [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] internal static extern string FormatVolume(string letter, string formatType, uint allocationSize, string volumeLabel); [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] internal static extern string DeletePartitions(uint driveIndex); /// /// Only supports REG_SZ, REG_MULTI_SZ (I think), DWORD, and QWORD value types. /// [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] internal static extern string GetValue(IntPtr data, string key, string valueName); /// /// Gets all REG_SZ, REG_MULTI_SZ (I think), DWORD, and QWORD value types. /// The values are delimited by '\n', and the value name is separated from /// the value with the following 3 wide string: ":|:" /// [DllImport("client-helper.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] internal static extern string GetValues(IntPtr data, string key); internal static ulong GetDWordValue(IntPtr data, string key, string valueName) => uint.Parse(GetValue(data, key, valueName), NumberStyles.HexNumber); internal static ulong GetQWordValue(IntPtr data, string key, string valueName) => ulong.Parse(GetValue(data, key, valueName), NumberStyles.HexNumber); } internal static class Win32 { // Win32 Functions [Flags] internal enum CM_GETIDLIST_FILTER : uint { ENUMERATOR = 0x00000001, SERVICE = 0x00000002, EJECTRELATIONS = 0x00000004, REMOVALRELATIONS = 0x00000008, POWERRELATIONS = 0x00000010, BUSRELATIONS = 0x00000020, NONE = 0x00000000, DONOTGENERATE = 0x10000040, TRANSPORTRELATIONS = 0x00000080, PRESENT = 0x00000100, CLASS = 0x00000200, BITS = 0x100003FF, } internal enum PNP_VETO_TYPE { Ok, TypeUnknown, LegacyDevice, PendingClose, WindowsApp, WindowsService, OutstandingOpen, Device, Driver, IllegalDeviceRequest, InsufficientPower, NonDisableable, LegacyDriver, } [DllImport("setupapi.dll")] internal static extern int CM_Request_Device_Eject( uint dnDevInst, out PNP_VETO_TYPE pVetoType, StringBuilder pszVetoName, int ulNameLength, int ulFlags ); [DllImport("CfgMgr32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern int CM_Get_Device_ID_List_Size(ref int length, string filter, CM_GETIDLIST_FILTER flags); [DllImport("CfgMgr32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern int CM_Get_Device_ID_List(string filter, byte[] buffer, int bufferLength, CM_GETIDLIST_FILTER flags); [DllImport("CfgMgr32.dll", CharSet = CharSet.Unicode)] internal static extern int CM_Get_Parent( out int pdnDevInst, int dnDevInst, int ulFlags); [DllImport("kernel32.dll")] internal static extern bool DefineDosDevice(uint dwFlags, string lpDeviceName, string lpTargetPath); [DllImport("Kernel32.dll")] internal static extern uint QueryDosDevice(string lpDeviceName, string lpTargetPath, uint ucchMax); internal const uint DDD_RAW_TARGET_PATH = 0x00000001; internal const uint DDD_REMOVE_DEFINITION = 0x00000002; internal const uint DDD_EXACT_MATCH_ON_REMOVE = 0x00000004; internal const uint DDD_NO_BROADCAST_SYSTEM = 0x00000008; [DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)] internal static extern void ZeroMemory(IntPtr dest, uint size); [DllImport("setupapi.dll", CharSet = CharSet.Auto)] public static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, [MarshalAs(UnmanagedType.LPTStr)] string? Enumerator, IntPtr hwndParent, uint Flags); [DllImport("setupapi.dll", CharSet = CharSet.Auto)] public static extern IntPtr SetupDiGetClassDevs(IntPtr ClassGuid, [MarshalAs(UnmanagedType.LPTStr)] string? Enumerator, IntPtr hwndParent, uint Flags); [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, ref SP_DEVINFO_DATA devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData); [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SetupDiBuildDriverInfoList( IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, SPDIT driverType); [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SetupDiDestroyDriverInfoList( IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, SPDIT driverType); [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SetupDiBuildDriverInfoList( IntPtr deviceInfoSet, IntPtr deviceInfoData, SPDIT driverType); [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SetupDiDestroyDriverInfoList( IntPtr deviceInfoSet, ref IntPtr deviceInfoData, SPDIT driverType); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SetupDiCreateDeviceInfoList(IntPtr ClassGuid, IntPtr hwndParent); [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr hDevInfo, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, UInt32 deviceInterfaceDetailDataSize, ref UInt32 requiredSize, IntPtr deviceInfoData); [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr hDevInfo, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, UInt32 deviceInterfaceDetailDataSize, ref UInt32 requiredSize, IntPtr deviceInfoData); [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern Boolean SetupDiEnumDeviceInfo(IntPtr hDevInfo, UInt32 memberIndex, ref SP_DEVINFO_DATA devInfo); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool SetupDiEnumDriverInfo( IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, SPDIT DriverType, int MemberIndex, ref SP_DRVINFO_DATA DriverInfoData); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool SetupDiEnumDriverInfo( IntPtr DeviceInfoSet, IntPtr DeviceInfoData, SPDIT DriverType, int MemberIndex, ref SP_DRVINFO_DATA DriverInfoData); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool SetupDiGetDriverInfoDetail( IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref SP_DRVINFO_DATA DriverInfoData, ref SP_DRVINFO_DETAIL_DATA DriverInfoDetailData, Int32 DriverInfoDetailDataSize, out Int32 RequiredSize); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool SetupDiGetDriverInfoDetail( IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref SP_DRVINFO_DATA DriverInfoData, ref SP_DRVINFO_DETAIL_DATA DriverInfoDetailData, Int32 DriverInfoDetailDataSize, out Int32 RequiredSize); [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean SetupDiGetDeviceRegistryProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, UInt32 property, out UInt32 propertyRegDataType, IntPtr propertyBuffer, UInt32 propertyBufferSize, out UInt32 requiredSize); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool SetupDiGetDevicePropertyW(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY PropertyKey, out uint PropertyType, IntPtr PropertyBuffer, uint PropertyBufferSize, out uint RequiredSize, uint Flags); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool SetupDiGetDeviceInstanceId(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, StringBuilder DeviceInstanceId, uint DeviceInstanceIdSize, out uint RequiredSize); [DllImport("setupapi.dll", SetLastError = false)] public static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool SetupDiGetDeviceInstallParams(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref SP_DEVINSTALL_PARAMS DeviceInstallParams); [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool SetupDiSetDeviceInstallParams(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref SP_DEVINSTALL_PARAMS DeviceInstallParams); [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool SetupGetInfDriverStoreLocation( [MarshalAs(UnmanagedType.LPTStr)] string FileName, Int32 AlternatePlatformInfo, Int32 LocalName, StringBuilder ReturnBuffer, Int32 ReturnBufferSize, out Int32 RequiredSize); [DllImport("setupapi.dll", SetLastError = true)] public static extern bool SetupCopyOEMInf( string SourceInfFileName, string OEMSourceMediaLocation, SPOST OEMSourceMediaType, SP_COPY CopyStyle, IntPtr DestinationInfFileName, int DestinationInfFileNameSize, ref int RequiredSize, IntPtr DestinationInfFileNameComponent ); [DllImport("CfgMgr32.dll", CharSet = CharSet.Unicode)] public static extern int CM_Get_DevNode_Property_Keys(UInt32 dnDevInst, [Out] IntPtr propertyKeyArray, ref UInt32 propertyKeyCount, UInt32 flags); [DllImport("CfgMgr32.dll", CharSet = CharSet.Unicode)] public static extern int CM_Get_DevNode_Property(UInt32 dnDevInst, ref DEVPROPKEY propertyKey, out UInt32 propertyType, IntPtr propertyBuffer, ref UInt32 propertyBufferSize, UInt32 flags); [DllImport("setupapi.dll", SetLastError = true)] public static extern int CM_Get_Device_ID_Size(out uint pulLen, UInt32 dnDevInst, int flags = 0); [DllImport("setupapi.dll", SetLastError = true)] public static extern int CM_Get_Device_ID(uint dnDevInst, StringBuilder Buffer, int BufferLen, int ulFlags = 0); [DllImport("setupapi.dll", SetLastError = true)] public static extern int CM_Get_Child(out uint pdnDevInst, UInt32 dnDevInst, int ulFlags = 0); [DllImport("setupapi.dll", SetLastError = true)] public static extern int CM_Get_Sibling(out uint pdnDevInst, UInt32 dnDevInst, int ulFlags = 0); [DllImport("setupapi.dll", SetLastError = true)] public static extern int CM_Locate_DevNodeA(ref uint pdnDevInst, string pDeviceID, int ulFlags = 0); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] public static extern bool DeviceIoControl(SafeFileHandle hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] public static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, ref DRIVE_LAYOUT_INFORMATION_EX lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern SafeFileHandle CreateFile( [MarshalAs(UnmanagedType.LPTStr)] string filename, [MarshalAs(UnmanagedType.U4)] FileAccess access, [MarshalAs(UnmanagedType.U4)] FileShare share, IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, IntPtr templateFile); [DllImport("kernel32.dll")] public static extern bool WriteFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, [In] ref System.Threading.NativeOverlapped lpOverlapped); [DllImport("kernel32.dll")] public static extern bool WriteFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, [In] IntPtr lpOverlapped); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool FlushFileBuffers(SafeFileHandle hFile); [DllImport("kernel32.dll", SetLastError = true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", EntryPoint = "RtlFillMemory", SetLastError = false)] public static extern void FillMemory(IntPtr destination, uint length, byte fill); [DllImport("newdev.dll", SetLastError = true)] public static extern bool UpdateDriverForPlugAndPlayDevices( IntPtr hWndParent, string hardwareId, string fullInfPath, INSTALLFLAG installFlags, out bool rebootRequired ); [DllImport("newdev.dll", SetLastError = true)] public static extern bool DiInstallDriver( IntPtr hwndParent, string FullInfPath, DIIRFLAG Flags, // either ZERO or FORCE_INF out bool NeedReboot ); // Win32 Items public enum INSTALLFLAG { FORCE = 0x00000001, READONLY = 0x00000002, NONINTERACTIVE = 0x00000004, BITS = 0x00000007 } [Flags] public enum DIIRFLAG { ZERO = 0x00000000, FORCE_INF = 0x00000002 } public enum SPOST : uint { NONE = 0, PATH = 1, URL = 2, MAX = 3 } [Flags] public enum SP_COPY : uint { DEFAULT = 0x0000000, // just to privide a 0 value DELETESOURCE = 0x0000001, // delete source file on successful copy REPLACEONLY = 0x0000002, // copy only if target file already present NEWER = 0x0000004, // copy only if source newer than or same as target NEWER_OR_SAME = NEWER, NOOVERWRITE = 0x0000008, // copy only if target doesn't exist NODECOMP = 0x0000010, // don't decompress source file while copying LANGUAGEAWARE = 0x0000020, // don't overwrite file of different language SOURCE_ABSOLUTE = 0x0000040, // SourceFile is a full source path SOURCEPATH_ABSOLUTE = 0x0000080, // SourcePathRoot is the full path IN_USE_NEEDS_REBOOT = 0x0000100, // System needs reboot if file in use FORCE_IN_USE = 0x0000200, // Force target-in-use behavior NOSKIP = 0x0000400, // Skip is disallowed for this file or section CABINETCONTINUATION = 0x0000800, // Used with need media notification FORCE_NOOVERWRITE = 0x0001000, // like NOOVERWRITE but no callback nofitication FORCE_NEWER = 0x0002000, // like NEWER but no callback nofitication WARNIFSKIP = 0x0004000, // system critical file: warn if user tries to skip NOBROWSE = 0x0008000, // Browsing is disallowed for this file or section NEWER_ONLY = 0x0010000 // copy only if source file newer than target } public const string GUID_DEVINTERFACE_USB_HUB = "{F18A0E88-C30C-11D0-8815-00A0C906BED8}"; public const string GUID_DEVINTERFACE_DISK = "{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}"; // Common Return Codes internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); internal static readonly int ERROR_INSUFFICIENT_BUFFER = 122; public const int CR_SUCCESS = 0; [StructLayout(LayoutKind.Sequential)] public struct DEVPROPKEY { public Guid Fmtid; public UInt32 Pid; } internal static class DevicePropertyTypes { internal const UInt32 DEVPROP_TYPEMOD_ARRAY = 0x00001000; internal const UInt32 DEVPROP_TYPEMOD_LIST = 0x00002000; internal const UInt32 DEVPROP_TYPE_EMPTY = 0x00000000; internal const UInt32 DEVPROP_TYPE_NULL = 0x00000001; internal const UInt32 DEVPROP_TYPE_SBYTE = 0x00000002; internal const UInt32 DEVPROP_TYPE_BYTE = 0x00000003; internal const UInt32 DEVPROP_TYPE_INT16 = 0x00000004; internal const UInt32 DEVPROP_TYPE_UINT16 = 0x00000005; internal const UInt32 DEVPROP_TYPE_INT32 = 0x00000006; internal const UInt32 DEVPROP_TYPE_UINT32 = 0x00000007; internal const UInt32 DEVPROP_TYPE_INT64 = 0x00000008; internal const UInt32 DEVPROP_TYPE_UINT64 = 0x00000009; internal const UInt32 DEVPROP_TYPE_FLOAT = 0x0000000A; internal const UInt32 DEVPROP_TYPE_DOUBLE = 0x0000000B; internal const UInt32 DEVPROP_TYPE_DECIMAL = 0x0000000C; internal const UInt32 DEVPROP_TYPE_GUID = 0x0000000D; internal const UInt32 DEVPROP_TYPE_CURRENCY = 0x0000000E; internal const UInt32 DEVPROP_TYPE_DATE = 0x0000000F; internal const UInt32 DEVPROP_TYPE_FILETIME = 0x00000010; internal const UInt32 DEVPROP_TYPE_BOOLEAN = 0x00000011; internal const UInt32 DEVPROP_TYPE_STRING = 0x00000012; internal const UInt32 DEVPROP_TYPE_STRING_LIST = DEVPROP_TYPE_STRING | DEVPROP_TYPEMOD_LIST; internal const UInt32 DEVPROP_TYPE_SECURITY_DESCRIPTOR = 0x00000013; internal const UInt32 DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING = 0x00000014; internal const UInt32 DEVPROP_TYPE_DEVPROPKEY = 0x00000015; internal const UInt32 DEVPROP_TYPE_DEVPROPTYPE = 0x00000016; internal const UInt32 DEVPROP_TYPE_BINARY = DEVPROP_TYPE_BYTE | DEVPROP_TYPEMOD_ARRAY; internal const UInt32 DEVPROP_TYPE_ERROR = 0x00000017; internal const UInt32 DEVPROP_TYPE_NTSTATUS = 0x00000018; internal const UInt32 DEVPROP_TYPE_STRING_INDIRECT = 0x00000019; internal const UInt32 MAX_DEVPROP_TYPE = 0x00000019; internal const UInt32 MAX_DEVPROP_TYPEMOD = 0x00002000; internal const UInt32 DEVPROP_MASK_TYPE = 0x00000FFF; internal const UInt32 DEVPROP_MASK_TYPEMOD = 0x0000F000; } internal static class DevicePropertyKeys { internal static readonly DEVPROPKEY DEVPKEY_NAME = new DEVPROPKEY() { Fmtid = new Guid(0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac), Pid = 10 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DeviceDesc = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_HardwareIds = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_Device_CompatibleIds = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Service = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Class = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 9 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ClassGuid = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 10 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Driver = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 11 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ConfigFlags = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 12 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Manufacturer = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 13 }; internal static readonly DEVPROPKEY DEVPKEY_Device_FriendlyName = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 14 }; internal static readonly DEVPROPKEY DEVPKEY_Device_LocationInfo = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 15 }; internal static readonly DEVPROPKEY DEVPKEY_Device_PDOName = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 16 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Capabilities = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 17 }; internal static readonly DEVPROPKEY DEVPKEY_Device_UINumber = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 18 }; internal static readonly DEVPROPKEY DEVPKEY_Device_UpperFilters = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 19 }; internal static readonly DEVPROPKEY DEVPKEY_Device_LowerFilters = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 20 }; internal static readonly DEVPROPKEY DEVPKEY_Device_BusTypeGuid = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 21 }; internal static readonly DEVPROPKEY DEVPKEY_Device_LegacyBusType = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 22 }; internal static readonly DEVPROPKEY DEVPKEY_Device_BusNumber = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 23 }; internal static readonly DEVPROPKEY DEVPKEY_Device_EnumeratorName = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 24 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Security = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 25 }; internal static readonly DEVPROPKEY DEVPKEY_Device_SecuritySDS = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 26 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DevType = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 27 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Exclusive = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 28 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Characteristics = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 29 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Address = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 30 }; internal static readonly DEVPROPKEY DEVPKEY_Device_UINumberDescFormat = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 31 }; internal static readonly DEVPROPKEY DEVPKEY_Device_PowerData = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 32 }; internal static readonly DEVPROPKEY DEVPKEY_Device_RemovalPolicy = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 33 }; internal static readonly DEVPROPKEY DEVPKEY_Device_RemovalPolicyDefault = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 34 }; internal static readonly DEVPROPKEY DEVPKEY_Device_RemovalPolicyOverride = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 35 }; internal static readonly DEVPROPKEY DEVPKEY_Device_InstallState = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 36 }; internal static readonly DEVPROPKEY DEVPKEY_Device_LocationPaths = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 37 }; internal static readonly DEVPROPKEY DEVPKEY_Device_BaseContainerId = new DEVPROPKEY() { Fmtid = new Guid(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), Pid = 38 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DevNodeStatus = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ProblemCode = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_Device_EjectionRelations = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_Device_RemovalRelations = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 5 }; internal static readonly DEVPROPKEY DEVPKEY_Device_PowerRelations = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_Device_BusRelations = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 7 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Parent = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 8 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Children = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 9 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Siblings = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 10 }; internal static readonly DEVPROPKEY DEVPKEY_Device_TransportRelations = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 11 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ProblemStatus = new DEVPROPKEY() { Fmtid = new Guid(0x4340a6c5, 0x93fa, 0x4706, 0x97, 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7), Pid = 12 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Reported = new DEVPROPKEY() { Fmtid = new Guid(0x80497100, 0x8c73, 0x48b9, 0xaa, 0xd9, 0xce, 0x38, 0x7e, 0x19, 0xc5, 0x6e), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Legacy = new DEVPROPKEY() { Fmtid = new Guid(0x80497100, 0x8c73, 0x48b9, 0xaa, 0xd9, 0xce, 0x38, 0x7e, 0x19, 0xc5, 0x6e), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ContainerId = new DEVPROPKEY() { Fmtid = new Guid(0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_InLocalMachineContainer = new DEVPROPKEY() { Fmtid = new Guid(0x8c7ed206, 0x3f8a, 0x4827, 0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ModelId = new DEVPROPKEY() { Fmtid = new Guid(0x80d81ea6, 0x7473, 0x4b0c, 0x82, 0x16, 0xef, 0xc1, 0x1a, 0x2c, 0x4c, 0x8b), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_FriendlyNameAttributes = new DEVPROPKEY() { Fmtid = new Guid(0x80d81ea6, 0x7473, 0x4b0c, 0x82, 0x16, 0xef, 0xc1, 0x1a, 0x2c, 0x4c, 0x8b), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ManufacturerAttributes = new DEVPROPKEY() { Fmtid = new Guid(0x80d81ea6, 0x7473, 0x4b0c, 0x82, 0x16, 0xef, 0xc1, 0x1a, 0x2c, 0x4c, 0x8b), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_Device_PresenceNotForDevice = new DEVPROPKEY() { Fmtid = new Guid(0x80d81ea6, 0x7473, 0x4b0c, 0x82, 0x16, 0xef, 0xc1, 0x1a, 0x2c, 0x4c, 0x8b), Pid = 5 }; internal static readonly DEVPROPKEY DEVPKEY_Device_SignalStrength = new DEVPROPKEY() { Fmtid = new Guid(0x80d81ea6, 0x7473, 0x4b0c, 0x82, 0x16, 0xef, 0xc1, 0x1a, 0x2c, 0x4c, 0x8b), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_Device_IsAssociateableByUserAction = new DEVPROPKEY() { Fmtid = new Guid(0x80d81ea6, 0x7473, 0x4b0c, 0x82, 0x16, 0xef, 0xc1, 0x1a, 0x2c, 0x4c, 0x8b), Pid = 7 }; internal static readonly DEVPROPKEY DEVPKEY_Numa_Proximity_Domain = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 1 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DHP_Rebalance_Policy = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Numa_Node = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_Device_BusReportedDeviceDesc = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_Device_IsPresent = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 5 }; internal static readonly DEVPROPKEY DEVPKEY_Device_HasProblem = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ConfigurationId = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 7 }; internal static readonly DEVPROPKEY DEVPKEY_Device_ReportedDeviceIdsHash = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 8 }; internal static readonly DEVPROPKEY DEVPKEY_Device_PhysicalDeviceLocation = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 9 }; internal static readonly DEVPROPKEY DEVPKEY_Device_BiosDeviceName = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 10 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverProblemDesc = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 11 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DebuggerSafe = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 12 }; internal static readonly DEVPROPKEY DEVPKEY_Device_PostInstallInProgress = new DEVPROPKEY() { Fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2), Pid = 13 }; internal static readonly DEVPROPKEY DEVPKEY_Device_SessionId = new DEVPROPKEY() { Fmtid = new Guid(0x83da6326, 0x97a6, 0x4088, 0x94, 0x53, 0xa1, 0x92, 0x3f, 0x57, 0x3b, 0x29), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_Device_InstallDate = new DEVPROPKEY() { Fmtid = new Guid(0x83da6326, 0x97a6, 0x4088, 0x94, 0x53, 0xa1, 0x92, 0x3f, 0x57, 0x3b, 0x29), Pid = 100 }; internal static readonly DEVPROPKEY DEVPKEY_Device_FirstInstallDate = new DEVPROPKEY() { Fmtid = new Guid(0x83da6326, 0x97a6, 0x4088, 0x94, 0x53, 0xa1, 0x92, 0x3f, 0x57, 0x3b, 0x29), Pid = 101 }; internal static readonly DEVPROPKEY DEVPKEY_Device_LastArrivalDate = new DEVPROPKEY() { Fmtid = new Guid(0x83da6326, 0x97a6, 0x4088, 0x94, 0x53, 0xa1, 0x92, 0x3f, 0x57, 0x3b, 0x29), Pid = 102 }; internal static readonly DEVPROPKEY DEVPKEY_Device_LastRemovalDate = new DEVPROPKEY() { Fmtid = new Guid(0x83da6326, 0x97a6, 0x4088, 0x94, 0x53, 0xa1, 0x92, 0x3f, 0x57, 0x3b, 0x29), Pid = 103 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverDate = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverVersion = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverDesc = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverInfPath = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 5 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverInfSection = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverInfSectionExt = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 7 }; internal static readonly DEVPROPKEY DEVPKEY_Device_MatchingDeviceId = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 8 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverProvider = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 9 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverPropPageProvider = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 10 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverCoInstallers = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 11 }; internal static readonly DEVPROPKEY DEVPKEY_Device_RepropertyBufferPickerTags = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 12 }; internal static readonly DEVPROPKEY DEVPKEY_Device_RepropertyBufferPickerExceptions = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 13 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverRank = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 14 }; internal static readonly DEVPROPKEY DEVPKEY_Device_DriverLogoLevel = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 15 }; internal static readonly DEVPROPKEY DEVPKEY_Device_NoConnectSound = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 17 }; internal static readonly DEVPROPKEY DEVPKEY_Device_GenericDriverInstalled = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 18 }; internal static readonly DEVPROPKEY DEVPKEY_Device_AdditionalSoftwareRequested = new DEVPROPKEY() { Fmtid = new Guid(0xa8b865dd, 0x2e3d, 0x4094, 0xad, 0x97, 0xe5, 0x93, 0xa7, 0xc, 0x75, 0xd6), Pid = 19 }; internal static readonly DEVPROPKEY DEVPKEY_Device_SafeRemovalRequired = new DEVPROPKEY() { Fmtid = new Guid(0xafd97640, 0x86a3, 0x4210, 0xb6, 0x7c, 0x28, 0x9c, 0x41, 0xaa, 0xbe, 0x55), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_Device_SafeRemovalRequiredOverride = new DEVPROPKEY() { Fmtid = new Guid(0xafd97640, 0x86a3, 0x4210, 0xb6, 0x7c, 0x28, 0x9c, 0x41, 0xaa, 0xbe, 0x55), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_DrvPkg_Model = new DEVPROPKEY() { Fmtid = new Guid(0xcf73bb51, 0x3abf, 0x44a2, 0x85, 0xe0, 0x9a, 0x3d, 0xc7, 0xa1, 0x21, 0x32), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_DrvPkg_VendorWebSite = new DEVPROPKEY() { Fmtid = new Guid(0xcf73bb51, 0x3abf, 0x44a2, 0x85, 0xe0, 0x9a, 0x3d, 0xc7, 0xa1, 0x21, 0x32), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_DrvPkg_DetailedDescription = new DEVPROPKEY() { Fmtid = new Guid(0xcf73bb51, 0x3abf, 0x44a2, 0x85, 0xe0, 0x9a, 0x3d, 0xc7, 0xa1, 0x21, 0x32), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_DrvPkg_DocumentationLink = new DEVPROPKEY() { Fmtid = new Guid(0xcf73bb51, 0x3abf, 0x44a2, 0x85, 0xe0, 0x9a, 0x3d, 0xc7, 0xa1, 0x21, 0x32), Pid = 5 }; internal static readonly DEVPROPKEY DEVPKEY_DrvPkg_Icon = new DEVPROPKEY() { Fmtid = new Guid(0xcf73bb51, 0x3abf, 0x44a2, 0x85, 0xe0, 0x9a, 0x3d, 0xc7, 0xa1, 0x21, 0x32), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_DrvPkg_BrandingIcon = new DEVPROPKEY() { Fmtid = new Guid(0xcf73bb51, 0x3abf, 0x44a2, 0x85, 0xe0, 0x9a, 0x3d, 0xc7, 0xa1, 0x21, 0x32), Pid = 7 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_UpperFilters = new DEVPROPKEY() { Fmtid = new Guid(0x4321918b, 0xf69e, 0x470d, 0xa5, 0xde, 0x4d, 0x88, 0xc7, 0x5a, 0xd2, 0x4b), Pid = 19 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_LowerFilters = new DEVPROPKEY() { Fmtid = new Guid(0x4321918b, 0xf69e, 0x470d, 0xa5, 0xde, 0x4d, 0x88, 0xc7, 0x5a, 0xd2, 0x4b), Pid = 20 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_Security = new DEVPROPKEY() { Fmtid = new Guid(0x4321918b, 0xf69e, 0x470d, 0xa5, 0xde, 0x4d, 0x88, 0xc7, 0x5a, 0xd2, 0x4b), Pid = 25 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_SecuritySDS = new DEVPROPKEY() { Fmtid = new Guid(0x4321918b, 0xf69e, 0x470d, 0xa5, 0xde, 0x4d, 0x88, 0xc7, 0x5a, 0xd2, 0x4b), Pid = 26 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_DevType = new DEVPROPKEY() { Fmtid = new Guid(0x4321918b, 0xf69e, 0x470d, 0xa5, 0xde, 0x4d, 0x88, 0xc7, 0x5a, 0xd2, 0x4b), Pid = 27 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_Exclusive = new DEVPROPKEY() { Fmtid = new Guid(0x4321918b, 0xf69e, 0x470d, 0xa5, 0xde, 0x4d, 0x88, 0xc7, 0x5a, 0xd2, 0x4b), Pid = 28 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_Characteristics = new DEVPROPKEY() { Fmtid = new Guid(0x4321918b, 0xf69e, 0x470d, 0xa5, 0xde, 0x4d, 0x88, 0xc7, 0x5a, 0xd2, 0x4b), Pid = 29 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_Name = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_ClassName = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_Icon = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_ClassInstaller = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 5 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_PropPageProvider = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_NoInstallClass = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 7 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_NoDisplayClass = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 8 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_SilentInstall = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 9 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_NoUseClass = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 10 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_DefaultService = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 11 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_IconPath = new DEVPROPKEY() { Fmtid = new Guid(0x259abffc, 0x50a7, 0x47ce, 0xaf, 0x8, 0x68, 0xc9, 0xa7, 0xd7, 0x33, 0x66), Pid = 12 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_DHPRebalanceOptOut = new DEVPROPKEY() { Fmtid = new Guid(0xd14d3ef3, 0x66cf, 0x4ba2, 0x9d, 0x38, 0x0d, 0xdb, 0x37, 0xab, 0x47, 0x01), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceClass_ClassCoInstallers = new DEVPROPKEY() { Fmtid = new Guid(0x713d1703, 0xa2e2, 0x49f5, 0x92, 0x14, 0x56, 0x47, 0x2e, 0xf3, 0xda, 0x5c), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceInterface_FriendlyName = new DEVPROPKEY() { Fmtid = new Guid(0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceInterface_Enabled = new DEVPROPKEY() { Fmtid = new Guid(0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceInterface_ClassGuid = new DEVPROPKEY() { Fmtid = new Guid(0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22), Pid = 4 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceInterface_ReferenceString = new DEVPROPKEY() { Fmtid = new Guid(0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22), Pid = 5 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceInterface_Restricted = new DEVPROPKEY() { Fmtid = new Guid(0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22), Pid = 6 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceInterfaceClass_DefaultInterface = new DEVPROPKEY() { Fmtid = new Guid(0x14c83a99, 0x0b3f, 0x44b7, 0xbe, 0x4c, 0xa1, 0x78, 0xd3, 0x99, 0x05, 0x64), Pid = 2 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceInterfaceClass_Name = new DEVPROPKEY() { Fmtid = new Guid(0x14c83a99, 0x0b3f, 0x44b7, 0xbe, 0x4c, 0xa1, 0x78, 0xd3, 0x99, 0x05, 0x64), Pid = 3 }; internal static readonly DEVPROPKEY DEVPKEY_Device_Model = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 39 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Address = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 51 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_DiscoveryMethod = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 52 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsEncrypted = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 53 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsAuthenticated = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 54 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsConnected = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 55 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsPaired = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 56 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Icon = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 57 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Version = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 65 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Last_Seen = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 66 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Last_Connected = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 67 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsShowInDisconnectedState = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 68 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsLocalMachine = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 70 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_MetadataPath = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 71 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsMetadataSearchInProgress = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 72 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_MetadataChecksum = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 73 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsNotInterestingForDisplay = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 74 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_LaunchDeviceStageOnDeviceConnect = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 76 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_LaunchDeviceStageFromExplorer = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 77 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_BaselineExperienceId = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 78 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsDeviceUniquelyIdentifiable = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 79 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_AssociationArray = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 80 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_DeviceDescription1 = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 81 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_DeviceDescription2 = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 82 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_HasProblem = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 83 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsSharedDevice = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 84 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsNetworkDevice = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 85 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsDefaultDevice = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 86 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_MetadataCabinet = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 87 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_RequiresPairingElevation = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 88 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_ExperienceId = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 89 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Category = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 90 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Category_Desc_Singular = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 91 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Category_Desc_Plural = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 92 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Category_Icon = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 93 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_CategoryGroup_Desc = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 94 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_CategoryGroup_Icon = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 95 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_PrimaryCategory = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 97 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_UnpairUninstall = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 98 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_RequiresUninstallElevation = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 99 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_DeviceFunctionSubRank = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 100 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_AlwaysShowDeviceAsConnected = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 101 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_ConfigFlags = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 105 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_PrivilegedPackageFamilyNames = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 106 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_CustomPrivilegedPackageFamilyNames = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 107 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_IsRebootRequired = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 108 }; internal static readonly DEVPROPKEY DEVPKEY_Device_InstanceId = new DEVPROPKEY() { Fmtid = new Guid(0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57), Pid = 256 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_FriendlyName = new DEVPROPKEY() { Fmtid = new Guid(0x656A3BB3, 0xECC0, 0x43FD, 0x84, 0x77, 0x4A, 0xE0, 0x40, 0x4A, 0x96, 0xCD), Pid = 12288 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_Manufacturer = new DEVPROPKEY() { Fmtid = new Guid(0x656A3BB3, 0xECC0, 0x43FD, 0x84, 0x77, 0x4A, 0xE0, 0x40, 0x4A, 0x96, 0xCD), Pid = 8192 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_ModelName = new DEVPROPKEY() { Fmtid = new Guid(0x656A3BB3, 0xECC0, 0x43FD, 0x84, 0x77, 0x4A, 0xE0, 0x40, 0x4A, 0x96, 0xCD), Pid = 8194 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_ModelNumber = new DEVPROPKEY() { Fmtid = new Guid(0x656A3BB3, 0xECC0, 0x43FD, 0x84, 0x77, 0x4A, 0xE0, 0x40, 0x4A, 0x96, 0xCD), Pid = 8195 }; internal static readonly DEVPROPKEY DEVPKEY_DeviceContainer_InstallInProgress = new DEVPROPKEY() { Fmtid = new Guid(0x83da6326, 0x97a6, 0x4088, 0x94, 0x53, 0xa1, 0x92, 0x3f, 0x57, 0x3b, 0x29), Pid = 9 }; } [Flags] public enum DiGetClassFlags : uint { DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE DIGCF_PRESENT = 0x00000002, DIGCF_ALLCLASSES = 0x00000004, DIGCF_PROFILE = 0x00000008, DIGCF_DEVICEINTERFACE = 0x00000010, } [Flags] public enum CmFlags : uint { CM_GETIDLIST_FILTER_SERVICE = 0x00000002, CM_GETIDLIST_FILTER_PRESENT = 0x00000100, } [StructLayout(LayoutKind.Sequential)] public struct SP_DEVICE_INTERFACE_DATA { public Int32 cbSize; public Guid interfaceClassGuid; public Int32 flags; public UIntPtr reserved; } [StructLayout(LayoutKind.Sequential)] public struct SP_DEVINFO_DATA { public UInt32 cbSize; public Guid ClassGuid; public UInt32 DevInst; public IntPtr Reserved; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct SP_DEVICE_INTERFACE_DETAIL_DATA { public int cbSize; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string DevicePath; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct SP_DRVINFO_DATA { public int cbSize; public int DriverType; private IntPtr Reserved; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string Description; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string MfgName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string ProviderName; public FILETIME DriverDate; public long DriverVersion; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct SP_DRVINFO_DETAIL_DATA { public int cbSize; public FILETIME InfDate; public int CompatIDsOffset; public int CompatIDsLength; public IntPtr Reserved; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string SectionName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string InfFileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] public string DrvDescription; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)] public string HardwareID; }; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct SP_DEVINSTALL_PARAMS { public int cbSize; public int Flags; public int FlagsEx; public readonly IntPtr hwndParent; public readonly IntPtr InstallMsgHandler; public readonly IntPtr InstallMsgHandlerContext; public readonly IntPtr FileQueue; public readonly IntPtr ClassInstallReserved; public readonly UIntPtr Reserved; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string DriverPath; public void Initialize() { cbSize = Marshal.SizeOf(typeof(SP_DEVINSTALL_PARAMS)); } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct SP_ORIGINAL_FILE_INFO { /// Size of this structure, in bytes. public uint cbSize; /// Original file name of the INF file stored in array of size MAX_PATH. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string OriginalInfName; /// Catalog name of the INF file stored in array of size MAX_PATH. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string OriginalCatalogName; } [StructLayout(LayoutKind.Sequential)] public struct FILETIME { public uint dwLowDateTime; public uint dwHighDateTime; } public enum SPDIT { NODRIVER = 0x00000000, CLASSDRIVER = 0x00000001, COMPATDRIVER = 0x00000002, } const ulong CM_GETIDLIST_FILTER_PRESENT = 0x00000100; const ulong CM_GETIDLIST_FILTER_SERVICE = 0x00000002; internal enum SPDRP { SPDRP_DEVICEDESC = 0, SPDRP_HARDWAREID = 0x1, SPDRP_COMPATIBLEIDS = 0x2, SPDRP_UNUSED0 = 0x3, SPDRP_SERVICE = 0x4, SPDRP_UNUSED1 = 0x5, SPDRP_UNUSED2 = 0x6, SPDRP_CLASS = 0x7, SPDRP_CLASSGUID = 0x8, SPDRP_DRIVER = 0x9, SPDRP_CONFIGFLAGS = 0xa, SPDRP_MFG = 0xb, SPDRP_FRIENDLYNAME = 0xc, SPDRP_LOCATION_INFORMATION = 0xd, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0xe, SPDRP_CAPABILITIES = 0xf, SPDRP_UI_NUMBER = 0x10, SPDRP_UPPERFILTERS = 0x11, SPDRP_LOWERFILTERS = 0x12, SPDRP_BUSTYPEGUID = 0x13, SPDRP_LEGACYBUSTYPE = 0x14, SPDRP_BUSNUMBER = 0x15, SPDRP_ENUMERATOR_NAME = 0x16, SPDRP_SECURITY = 0x17, SPDRP_SECURITY_SDS = 0x18, SPDRP_DEVTYPE = 0x19, SPDRP_EXCLUSIVE = 0x1a, SPDRP_CHARACTERISTICS = 0x1b, SPDRP_ADDRESS = 0x1c, SPDRP_UI_NUMBER_DESC_FORMAT = 0x1e, SPDRP_MAXIMUM_PROPERTY = 0x1f } [Flags] internal enum FileAccess : uint { AccessSystemSecurity = 0x1000000, MaximumAllowed = 0x2000000, Delete = 0x10000, ReadControl = 0x20000, WriteDAC = 0x40000, WriteOwner = 0x80000, Synchronize = 0x100000, StandardRightsRequired = 0xF0000, StandardRightsRead = ReadControl, StandardRightsWrite = ReadControl, StandardRightsExecute = ReadControl, StandardRightsAll = 0x1F0000, SpecificRightsAll = 0xFFFF, FILE_READ_DATA = 0x0001, // file & pipe FILE_LIST_DIRECTORY = 0x0001, // directory FILE_WRITE_DATA = 0x0002, // file & pipe FILE_ADD_FILE = 0x0002, // directory FILE_APPEND_DATA = 0x0004, // file FILE_ADD_SUBDIRECTORY = 0x0004, // directory FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe FILE_READ_EA = 0x0008, // file & directory FILE_WRITE_EA = 0x0010, // file & directory FILE_EXECUTE = 0x0020, // file FILE_TRAVERSE = 0x0020, // directory FILE_DELETE_CHILD = 0x0040, // directory FILE_READ_ATTRIBUTES = 0x0080, // all FILE_WRITE_ATTRIBUTES = 0x0100, // all // // Generic Section // GenericRead = 0x80000000, GenericWrite = 0x40000000, GenericExecute = 0x20000000, GenericAll = 0x10000000, SPECIFIC_RIGHTS_ALL = 0x00FFFF, FILE_ALL_ACCESS = StandardRightsRequired | Synchronize | 0x1FF, FILE_GENERIC_READ = StandardRightsRead | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | Synchronize, FILE_GENERIC_WRITE = StandardRightsWrite | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | Synchronize, FILE_GENERIC_EXECUTE = StandardRightsExecute | FILE_READ_ATTRIBUTES | FILE_EXECUTE | Synchronize } [Flags] internal enum FileShare : uint { /// /// /// None = 0x00000000, /// /// Enables subsequent open operations on an object to request read access. /// Otherwise, other processes cannot open the object if they request read access. /// If this flag is not specified, but the object has been opened for read access, the function fails. /// Read = 0x00000001, /// /// Enables subsequent open operations on an object to request write access. /// Otherwise, other processes cannot open the object if they request write access. /// If this flag is not specified, but the object has been opened for write access, the function fails. /// Write = 0x00000002, /// /// Enables subsequent open operations on an object to request delete access. /// Otherwise, other processes cannot open the object if they request delete access. /// If this flag is not specified, but the object has been opened for delete access, the function fails. /// Delete = 0x00000004 } internal enum FileMode : uint { /// /// Creates a new file. The function fails if a specified file exists. /// New = 1, /// /// Creates a new file, always. /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies. /// CreateAlways = 2, /// /// Opens a file. The function fails if the file does not exist. /// OpenExisting = 3, /// /// Opens a file, always. /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW. /// OpenAlways = 4, /// /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist. /// The calling process must open the file with the GENERIC_WRITE access right. /// TruncateExisting = 5 } [Flags] internal enum FileAttributes : uint { Readonly = 0x00000001, Hidden = 0x00000002, System = 0x00000004, Directory = 0x00000010, Archive = 0x00000020, Device = 0x00000040, Normal = 0x00000080, Temporary = 0x00000100, SparseFile = 0x00000200, ReparsePoint = 0x00000400, Compressed = 0x00000800, Offline = 0x00001000, NotContentIndexed = 0x00002000, Encrypted = 0x00004000, Write_Through = 0x80000000, Overlapped = 0x40000000, NoBuffering = 0x20000000, RandomAccess = 0x10000000, SequentialScan = 0x08000000, DeleteOnClose = 0x04000000, BackupSemantics = 0x02000000, PosixSemantics = 0x01000000, OpenReparsePoint = 0x00200000, OpenNoRecall = 0x00100000, FirstPipeInstance = 0x00080000 } [StructLayout(LayoutKind.Explicit, Size = 8)] internal struct LARGE_INTEGER { [FieldOffset(0)] internal int Low; [FieldOffset(4)] internal int High; [FieldOffset(0)] internal long QuadPart; // use only when QuadPart canot be passed internal long ToInt64() { return ((long)this.High << 32) | (uint)this.Low; } // just for demonstration internal static LARGE_INTEGER FromInt64(long value) { return new LARGE_INTEGER { Low = (int)(value), High = (int)((value >> 32)), }; } } [StructLayout(LayoutKind.Sequential)] internal class STORAGE_DEVICE_NUMBER { internal uint DeviceType; internal uint DeviceNumber; internal uint PartitionNumber; } [StructLayout(LayoutKind.Sequential, Size = 8)] internal class DISK_EXTENT { internal uint DiskNumber; internal long StartingOffset; internal long ExtentLength; } [StructLayout(LayoutKind.Sequential)] internal class VOLUME_DISK_EXTENTS { internal uint NumberOfDiskExtents; internal DISK_EXTENT Extents; } /// /// Describes the geometry of disk devices and media. /// [StructLayout(LayoutKind.Explicit)] internal struct DISK_GEOMETRY { /// /// The number of cylinders. /// [FieldOffset(0)] internal Int64 Cylinders; /// /// The type of media. For a list of values, see MEDIA_TYPE. /// [FieldOffset(8)] internal MEDIA_TYPE MediaType; /// /// The number of tracks per cylinder. /// [FieldOffset(12)] internal uint TracksPerCylinder; /// /// The number of sectors per track. /// [FieldOffset(16)] internal uint SectorsPerTrack; /// /// The number of bytes per sector. /// [FieldOffset(20)] internal uint BytesPerSector; } /* /// /// Describes the extended geometry of disk devices and media. /// [StructLayout(LayoutKind.Explicit)] internal struct DISK_GEOMETRY_EX { /// /// A DISK_GEOMETRY structure. /// [FieldOffset(0)] public DISK_GEOMETRY Geometry; /// /// The disk size, in bytes. /// [FieldOffset(24)] public Int64 DiskSize; /// /// Any additional data. /// [FieldOffset(32)] public Byte Data; } */ [StructLayout(LayoutKind.Sequential)] internal class DISK_GEOMETRY_EX { internal DISK_GEOMETRY Geometry; internal LARGE_INTEGER DiskSize; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] internal byte[] Data; } internal enum MEDIA_TYPE : int { Unknown = 0, F5_1Pt2_512 = 1, F3_1Pt44_512 = 2, F3_2Pt88_512 = 3, F3_20Pt8_512 = 4, F3_720_512 = 5, F5_360_512 = 6, F5_320_512 = 7, F5_320_1024 = 8, F5_180_512 = 9, F5_160_512 = 10, RemovableMedia = 11, FixedMedia = 12, F3_120M_512 = 13, F3_640_512 = 14, F5_640_512 = 15, F5_720_512 = 16, F3_1Pt2_512 = 17, F3_1Pt23_1024 = 18, F5_1Pt23_1024 = 19, F3_128Mb_512 = 20, F3_230Mb_512 = 21, F8_256_128 = 22, F3_200Mb_512 = 23, F3_240M_512 = 24, F3_32M_512 = 25 } /// /// Represents the format of a partition. /// internal enum PARTITION_STYLE : uint { /// /// Master boot record (MBR) format. /// PARTITION_STYLE_MBR = 0, /// /// GUID Partition Table (GPT) format. /// PARTITION_STYLE_GPT = 1, /// /// Partition not formatted in either of the recognized formats—MBR or GPT. /// PARTITION_STYLE_RAW = 2 } /// /// Contains partition information specific to master boot record (MBR) disks. /// [StructLayout(LayoutKind.Explicit)] internal struct PARTITION_INFORMATION_MBR { #region Constants /// /// An unused entry partition. /// internal const byte PARTITION_ENTRY_UNUSED = 0x00; /// /// A FAT12 file system partition. /// internal const byte PARTITION_FAT_12 = 0x01; /// /// A FAT16 file system partition. /// internal const byte PARTITION_FAT_16 = 0x04; /// /// An extended partition. /// internal const byte PARTITION_EXTENDED = 0x05; /// /// An IFS partition. /// internal const byte PARTITION_IFS = 0x07; /// /// A FAT32 file system partition. /// internal const byte PARTITION_FAT32 = 0x0B; /// /// A logical disk manager (LDM) partition. /// internal const byte PARTITION_LDM = 0x42; /// /// An NTFT partition. /// internal const byte PARTITION_NTFT = 0x80; /// /// A valid NTFT partition. /// /// The high bit of a partition type code indicates that a partition is part of an NTFT mirror or striped array. /// internal const byte PARTITION_VALID_NTFT = 0xC0; #endregion /// /// The type of partition. For a list of values, see Disk Partition Types. /// [FieldOffset(0)] [MarshalAs(UnmanagedType.U1)] internal byte PartitionType; /// /// If this member is TRUE, the partition is bootable. /// [FieldOffset(1)] [MarshalAs(UnmanagedType.I1)] internal bool BootIndicator; /// /// If this member is TRUE, the partition is of a recognized type. /// [FieldOffset(2)] [MarshalAs(UnmanagedType.I1)] internal bool RecognizedPartition; /// /// The number of hidden sectors in the partition. /// [FieldOffset(4)] internal uint HiddenSectors; } /// /// Contains GUID partition table (GPT) partition information. /// [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] internal struct PARTITION_INFORMATION_GPT { /// /// A GUID that identifies the partition type. /// /// Each partition type that the EFI specification supports is identified by its own GUID, which is /// published by the developer of the partition. /// [FieldOffset(0)] internal Guid PartitionType; /// /// The GUID of the partition. /// [FieldOffset(16)] internal Guid PartitionId; /// /// The Extensible Firmware Interface (EFI) attributes of the partition. /// /// [FieldOffset(32)] internal UInt64 Attributes; /// /// A wide-character string that describes the partition. /// [FieldOffset(40)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 36)] internal string Name; } /// /// Provides information about a drive's master boot record (MBR) partitions. /// [StructLayout(LayoutKind.Explicit)] internal struct DRIVE_LAYOUT_INFORMATION_MBR { /// /// The signature of the drive. /// [FieldOffset(0)] internal uint Signature; } /// /// Contains information about a drive's GUID partition table (GPT) partitions. /// [StructLayout(LayoutKind.Explicit)] internal struct DRIVE_LAYOUT_INFORMATION_GPT { /// /// The GUID of the disk. /// [FieldOffset(0)] internal Guid DiskId; /// /// The starting byte offset of the first usable block. /// [FieldOffset(16)] internal Int64 StartingUsableOffset; /// /// The size of the usable blocks on the disk, in bytes. /// [FieldOffset(24)] internal Int64 UsableLength; /// /// The maximum number of partitions that can be defined in the usable block. /// [FieldOffset(32)] internal uint MaxPartitionCount; } /// /// Contains information about a disk partition. /// [StructLayout(LayoutKind.Explicit)] internal struct PARTITION_INFORMATION_EX { /// /// The format of the partition. For a list of values, see PARTITION_STYLE. /// [FieldOffset(0)] internal PARTITION_STYLE PartitionStyle; /// /// The starting offset of the partition. /// [FieldOffset(8)] internal Int64 StartingOffset; /// /// The length of the partition, in bytes. /// [FieldOffset(16)] internal Int64 PartitionLength; /// /// The number of the partition (1-based). /// [FieldOffset(24)] internal uint PartitionNumber; /// /// If this member is TRUE, the partition information has changed. When you change a partition (with /// IOCTL_DISK_SET_DRIVE_LAYOUT), the system uses this member to determine which partitions have changed /// and need their information rewritten. /// [FieldOffset(28)] [MarshalAs(UnmanagedType.I1)] internal bool RewritePartition; /// /// A PARTITION_INFORMATION_MBR structure that specifies partition information specific to master boot /// record (MBR) disks. The MBR partition format is the standard AT-style format. /// [FieldOffset(32)] internal PARTITION_INFORMATION_MBR Mbr; /// /// A PARTITION_INFORMATION_GPT structure that specifies partition information specific to GUID partition /// table (GPT) disks. The GPT format corresponds to the EFI partition format. /// [FieldOffset(32)] internal PARTITION_INFORMATION_GPT Gpt; } [StructLayout(LayoutKind.Explicit)] internal struct DRIVE_LAYOUT_INFORMATION_UNION { [FieldOffset(0)] internal DRIVE_LAYOUT_INFORMATION_MBR Mbr; [FieldOffset(0)] internal DRIVE_LAYOUT_INFORMATION_GPT Gpt; } /// /// Contains extended information about a drive's partitions. /// [StructLayout(LayoutKind.Explicit)] internal struct DRIVE_LAYOUT_INFORMATION_EX { /// /// The style of the partitions on the drive enumerated by the PARTITION_STYLE enumeration. /// [FieldOffset(0)] internal PARTITION_STYLE PartitionStyle; /// /// The number of partitions on a drive. /// /// On disks with the MBR layout, this value is always a multiple of 4. Any partitions that are unused have /// a partition type of PARTITION_ENTRY_UNUSED. /// [FieldOffset(4)] internal uint PartitionCount; /// /// A DRIVE_LAYOUT_INFORMATION_MBR structure containing information about the master boot record type /// partitioning on the drive. /// [FieldOffset(8)] internal DRIVE_LAYOUT_INFORMATION_UNION Mbr; /// /// A DRIVE_LAYOUT_INFORMATION_GPT structure containing information about the GUID disk partition type /// partitioning on the drive. /// [FieldOffset(8)] internal DRIVE_LAYOUT_INFORMATION_GPT Gpt; /// /// A variable-sized array of PARTITION_INFORMATION_EX structures, one structure for each partition on the /// drive. /// [FieldOffset(48)] [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 4)] internal PARTITION_INFORMATION_EX[] PartitionEntry; } [StructLayout(LayoutKind.Explicit)] internal struct CREATE_DISK_MBR { [FieldOffset(0)] internal uint Signature; } [StructLayout(LayoutKind.Explicit)] internal struct CREATE_DISK_GPT { [FieldOffset(0)] internal Guid DiskId; [FieldOffset(16)] internal uint MaxPartitionCount; } [StructLayout(LayoutKind.Explicit)] internal struct CREATE_DISK { [FieldOffset(0)] internal PARTITION_STYLE PartitionStyle; [FieldOffset(4)] internal CREATE_DISK_MBR Mbr; [FieldOffset(4)] internal CREATE_DISK_GPT Gpt; } internal const int DRIVE_ACCESS_RETRIES = 10; internal const int DRIVE_ACCESS_TIMEOUT = 15000; internal const int MIN_EXTRA_PART_SIZE = 1024 * 1024; internal class IoCtl /* constants */ { internal const UInt32 DISK_BASE = 0x00000007, VOLUME_BASE = 0x00000056, STORAGE_BASE = 0x0000002d, FILE_DEVICE_DISK_SYSTEM = 0x00000008, FILE_DEVICE_FILE_SYSTEM = 0x00000009, METHOD_BUFFERED = 0, METHOD_IN_DIRECT = 1, METHOD_OUT_DIRECT = 2, METHOD_NEITHER = 3, FILE_READ_ACCESS = 0x0001, FILE_WRITE_ACCESS = 0x0002, FILE_ANY_ACCESS = 0; internal const UInt32 GENERIC_READ = 0x80000000, FILE_SHARE_WRITE = 0x2, FILE_SHARE_READ = 0x1, OPEN_EXISTING = 0x3; internal static readonly UInt32 DISK_FLUSH_CACHE = IoCtl.CTL_CODE(DISK_BASE, 0x715, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 DISK_GET_DRIVE_LAYOUT_EX = IoCtl.CTL_CODE(DISK_BASE, 0x0014, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 DISK_SET_DRIVE_LAYOUT_EX = IoCtl.CTL_CODE(DISK_BASE, 0x0015, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS); internal static readonly UInt32 DISK_DELETE_DRIVE_LAYOUT = IoCtl.CTL_CODE(DISK_BASE, 0x0040, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS); internal static readonly UInt32 DISK_CREATE_DISK = IoCtl.CTL_CODE(DISK_BASE, 0x0016, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS); internal static readonly UInt32 DISK_UPDATE_PROPERTIES = IoCtl.CTL_CODE(DISK_BASE, 0x0050, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 DISK_GET_DRIVE_GEOMETRY_EX = IoCtl.CTL_CODE(DISK_BASE, 0x0028, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 DISK_GET_DRIVE_GEOMETRY = IoCtl.CTL_CODE(DISK_BASE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 FSCTL_ALLOW_EXTENDED_DASD_IO = IoCtl.CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 32, METHOD_NEITHER, FILE_ANY_ACCESS); internal static readonly UInt32 FSCTL_LOCK_VOLUME = IoCtl.CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 FSCTL_UNLOCK_VOLUME = IoCtl.CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 7, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 FSCTL_DISMOUNT_VOLUME = IoCtl.CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 8, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 VOLUME_ONLINE = IoCtl.CTL_CODE(VOLUME_BASE, 2, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS); internal static readonly UInt32 VOLUME_OFFLINE = IoCtl.CTL_CODE(VOLUME_BASE, 3, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS); internal static readonly UInt32 VOLUME_GET_VOLUME_DISK_EXTENTS = IoCtl.CTL_CODE(VOLUME_BASE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static readonly UInt32 STORAGE_GET_DEVICE_NUMBER = IoCtl.CTL_CODE(STORAGE_BASE, 0x0420, METHOD_BUFFERED, FILE_ANY_ACCESS); internal static UInt32 CTL_CODE(UInt32 DeviceType, UInt32 Function, UInt32 Method, UInt32 Access) { return (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)); } } public class SafeHGlobalHandle : SafeHandle { public SafeHGlobalHandle(IntPtr handle) : base(IntPtr.Zero, true) { SetHandle(handle); } public override bool IsInvalid { get => handle == IntPtr.Zero; } protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); return true; } } public static class FileLock { public static bool HasKilledExplorer = false; private const int RmRebootReasonNone = 0; private const int CCH_RM_MAX_APP_NAME = 255; private const int CCH_RM_MAX_SVC_NAME = 63; [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] private static extern int RmRegisterResources(uint pSessionHandle, uint nFiles, string[] rgsFilenames, uint nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, uint nServices, string[] rgsServiceNames); [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); [DllImport("rstrtmgr.dll")] private static extern int RmEndSession(uint pSessionHandle); [DllImport("rstrtmgr.dll")] private static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In] [Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons); public static List WhoIsLocking(string path) { string key = Guid.NewGuid().ToString(); List processes = new List(); int res = RmStartSession(out uint handle, 0, key); if (res != 0) { throw new Exception("Could not begin restart session. Unable to determine file locker."); } try { const int ERROR_MORE_DATA = 234; uint pnProcInfoNeeded = 0, pnProcInfo = 0, lpdwRebootReasons = RmRebootReasonNone; string[] resources = new string[] { path }; // Just checking on one resource. res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); if (res != 0) throw new Exception("Could not register resource."); //Note: there's a race condition here -- the first call to RmGetList() returns // the total number of process. However, when we call RmGetList() again to get // the actual processes this number may have increased. res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); if (res == ERROR_MORE_DATA) { // Create an array to store the process results RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded + 3]; pnProcInfo = pnProcInfoNeeded; // Get the list res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); if (res == 0) { processes = new List((int)pnProcInfo); // Enumerate all of the results and add them to the // list to be returned for (int i = 0; i < pnProcInfo; i++) { try { processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); } // catch the error -- in case the process is no longer running catch (ArgumentException) { } } } else throw new Exception("Could not list processes locking resource: " + res); } else if (res != 0) throw new Exception("Could not list processes locking resource. Could not get size of result." + $" Result value: {res}"); } finally { RmEndSession(handle); } return processes; } [StructLayout(LayoutKind.Sequential)] private struct RM_UNIQUE_PROCESS { public readonly int dwProcessId; public readonly FILETIME ProcessStartTime; } private enum RM_APP_TYPE { RmUnknownApp = 0, RmMainWindow = 1, RmOtherWindow = 2, RmService = 3, RmExplorer = 4, RmConsole = 5, RmCritical = 1000 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct RM_PROCESS_INFO { public readonly RM_UNIQUE_PROCESS Process; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] public readonly string strAppName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] public readonly string strServiceShortName; public readonly RM_APP_TYPE ApplicationType; public readonly uint AppStatus; public readonly uint TSSessionId; [MarshalAs(UnmanagedType.Bool)] public readonly bool bRestartable; } } } } ================================================ FILE: TrustedUninstaller.Shared/USB/OSDownload.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Security.Cryptography; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Core; using Downloader; using Microsoft.Win32; using TimeZoneConverter; namespace iso_mode { public class OSDownload { public static string HumanReadableBytes(double input) { double size = input; string[] suffixes = { "B", "KB", "MB", "GB", "TB", "PB" }; if (size == 0) return "0 MB"; int order = 0; while (size >= 1024 && order < suffixes.Length - 1) { order++; size = size / 1024; } double roundedAndFormattedSize = Math.Round((double)size, 0); return $"{roundedAndFormattedSize} {suffixes[order]}"; } private static readonly Dictionary _languageDictionary = new Dictionary { { "0401", "Arabic" }, { "0416", "Brazilian Portuguese" }, { "0402", "Bulgarian" }, { "0804", "Chinese (Simplified)" }, { "0404", "Chinese (Traditional)" }, { "041A", "Croatian" }, { "0405", "Czech" }, { "0406", "Danish" }, { "0413", "Dutch" }, { "0409", "English (United States)" }, { "0809", "English International" }, { "0425", "Estonian" }, { "040B", "Finnish" }, { "040C", "French" }, { "0C0C", "French Canadian" }, { "0407", "German" }, { "0408", "Greek" }, { "040D", "Hebrew" }, { "040E", "Hungarian" }, { "0410", "Italian" }, { "0411", "Japanese" }, { "0412", "Korean" }, { "0426", "Latvian" }, { "0427", "Lithuanian" }, { "0414", "Norwegian" }, { "0415", "Polish" }, { "0816", "Portuguese" }, { "0418", "Romanian" }, { "0419", "Russian" }, { "081A", "Serbian Latin" }, { "041B", "Slovak" }, { "0424", "Slovenian" }, { "0C0A", "Spanish" }, { "080A", "Spanish (Mexico)" }, { "041D", "Swedish" }, { "041E", "Thai" }, { "041F", "Turkish" }, { "0422", "Ukrainian" } }; public static string GetInstalledLanguageName() { using (var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Nls\Language")) { if (key != null) { var installLanguage = key.GetValue("InstallLanguage") as string; if (installLanguage != null && _languageDictionary.ContainsKey(installLanguage)) { return _languageDictionary[installLanguage]; } } return "English (United States)"; } } public enum OS { Windows, Ubuntu, Arch, SteamOS, } public static async Task<(string Link, string? Version, string? Hash)> GetDownloadLinkAsyncResilient(OS os) { for (int i = 0; i < 3; i++) { if (i == 2) return await GetOSDownloadLinkTask(os); else { try { return await GetOSDownloadLinkTask(os); } catch (Exception e) { Log.WriteExceptionSafe(e); if (e.Message == "Microsoft blocked the automated download request based on your IP address.") throw; } Thread.Sleep(500); } } throw new Exception("Could not fetch download link."); } private static Task<(string Link, string? Version, string? Hash)> GetOSDownloadLinkTask(OS os) => os switch { OS.Windows => GetWindowsDownloadLinkAsync(), OS.Ubuntu => GetUbuntuDownloadLinkAsync(), OS.Arch => GetArchDownloadLinkAsync(), OS.SteamOS => GetSteamOSDownloadLinkAsync(), _ => throw new Exception("Unknown OS") }; public static async Task DownloadISOAsync(string isoDownloadLink, string filePath, string? hash, CancellationToken cancellationToken, Action onProgressChanged) { using (var downloader = new DownloadService(new DownloadConfiguration() { Timeout = 5000, MaxTryAgainOnFailover = 5, RequestConfiguration = new RequestConfiguration() { UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", } })) { int progress = 0; string speedString = "0 MB"; downloader.DownloadProgressChanged += (sender, args) => { var newProgress = (int)Math.Round(args.ProgressPercentage); var newSpeedString = HumanReadableBytes(args.BytesPerSecondSpeed); if (newProgress != progress || newSpeedString != speedString) onProgressChanged.Invoke(newProgress, newSpeedString); progress = newProgress; speedString = newSpeedString; }; await downloader.DownloadFileTaskAsync(isoDownloadLink, filePath, cancellationToken); } if (cancellationToken.IsCancellationRequested) { Wrap.ExecuteSafe(() => File.Delete(filePath), true); return; } } private static async Task<(string Link, string? Version, string? Hash)> GetWindowsDownloadLinkAsync() { string language = GetInstalledLanguageName(); string isoDownloadLink; using var httpClient = new HttpClient() { Timeout = TimeSpan.FromMinutes(2) }; httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"); string sessionId = Guid.NewGuid().ToString(); string url = "https://www.microsoft.com/en-us/software-download/windows11"; string html = await httpClient.GetStringAsync(url); var match = Regex.Match(html, @"