From e0d0bfd030a63da2303e608f1379e59f1fb4400b Mon Sep 17 00:00:00 2001 From: he3als Date: Sat, 18 Nov 2023 16:31:35 +0000 Subject: [PATCH] feat: .NET 3.5 with USB/folder --- src/Actions/.NET.cs | 279 ++++++++++++++------------ src/Misc.FolderPicker/FolderPicker.cs | 224 +++++++++++++++++++++ src/amecs.csproj | 1 + 3 files changed, 372 insertions(+), 132 deletions(-) create mode 100644 src/Misc.FolderPicker/FolderPicker.cs diff --git a/src/Actions/.NET.cs b/src/Actions/.NET.cs index dd602ec..36ab4e7 100644 --- a/src/Actions/.NET.cs +++ b/src/Actions/.NET.cs @@ -8,6 +8,7 @@ using System.Security; using System.Text; using System.Threading; using System.Windows.Forms; +using Misc.FolderPicker; using Ameliorated.ConsoleUtils; using Microsoft.Dism; @@ -15,181 +16,195 @@ namespace amecs.Actions { public class _NET { - public static bool Install() + private static string file; + private static bool CheckFileViolation(string inputFile) { - var key = new ChoicePrompt() { Text = "A Windows ISO must be provided to install .NET 3.5.\r\nPress any key to select an ISO: ", AnyKey = true }.Start(); + try + { + file = inputFile; + } + catch (SecurityException e) + { + Console.WriteLine(); + ConsoleTUI.OpenFrame.Close("Security error: " + e.Message, ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt() { AnyKey = true, Text = "Press any key to return to the Menu: " }); + return true; + } - if (key == null) - return false; + return false; + } + + private static bool DismountImage() + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.CreateNoWindow = false; + startInfo.UseShellExecute = false; + startInfo.FileName = "PowerShell.exe"; + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.Arguments = $"-NoP -C \"Dismount-DiskImage '{file}'\""; + startInfo.RedirectStandardOutput = true; + + var proc = Process.Start(startInfo); + proc.WaitForExit(); + return true; + } + + public static bool Install() + { + string letter; - var dialog = new System.Windows.Forms.OpenFileDialog(); - dialog.Filter = "ISO Files (*.ISO)| *.ISO"; // Filter files by extension - dialog.Multiselect = false; - dialog.InitialDirectory = Globals.UserFolder; - - NativeWindow window = new NativeWindow(); - window.AssignHandle(Process.GetCurrentProcess().MainWindowHandle); - if (dialog.ShowDialog(window) == DialogResult.OK) + var choice = new ChoicePrompt() { Text = "A Windows Setup image must be provided to install .NET 3.5.\r\nWould you like to use a Windows Setup USB/folder instead of an ISO? (Y/N): " }.Start(); + if (choice == null) return false; + var usingFolder = choice == 0; + if (usingFolder) { - string file; - try - { - file = dialog.FileName; - } catch (SecurityException e) + var dlg = new FolderPicker(); + if (dlg.ShowDialog(default, false) == false) return false; + if (CheckFileViolation(dlg.ResultPath)) return false; + letter = dlg.ResultPath; + } + else + { + var dialog = new System.Windows.Forms.OpenFileDialog(); + dialog.Filter = "ISO Files (*.ISO)| *.ISO"; // Filter files by extension + dialog.Multiselect = false; + dialog.InitialDirectory = Globals.UserFolder; + + NativeWindow window = new NativeWindow(); + window.AssignHandle(Process.GetCurrentProcess().MainWindowHandle); + if (dialog.ShowDialog(window) == DialogResult.OK) { + if (CheckFileViolation(dialog.FileName)) return false; + Console.WriteLine(); - ConsoleTUI.OpenFrame.Close("Security error: " + e.Message, ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt() { AnyKey = true, Text = "Press any key to return to the Menu: " }); - return false; - } - - Console.WriteLine(); - ConsoleTUI.OpenFrame.WriteCentered("\r\nMounting ISO"); - - string letter; - using (new ConsoleUtils.LoadingIndicator(true)) - { - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.CreateNoWindow = false; - startInfo.UseShellExecute = false; - startInfo.FileName = "PowerShell.exe"; - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.Arguments = $"-NoP -C \"(Mount-DiskImage '{file}' -PassThru | Get-Volume).DriveLetter\""; - startInfo.RedirectStandardOutput = true; - - var proc = Process.Start(startInfo); - proc.WaitForExit(); - - letter = proc.StandardOutput.ReadLine(); - } - - - - - if (!Directory.Exists(letter + @":\sources\sxs") || !Directory.GetFiles(letter + @":\sources\sxs", "*netfx3*").Any()) - { - try + ConsoleTUI.OpenFrame.WriteCentered("\r\nMounting ISO"); + + using (new ConsoleUtils.LoadingIndicator(true)) { ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.CreateNoWindow = false; startInfo.UseShellExecute = false; startInfo.FileName = "PowerShell.exe"; startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.Arguments = $"-NoP -C \"Dismount-DiskImage '{file}'\""; + startInfo.Arguments = $"-NoP -C \"(Mount-DiskImage '{file}' -PassThru | Get-Volume).DriveLetter + ':'\""; startInfo.RedirectStandardOutput = true; var proc = Process.Start(startInfo); proc.WaitForExit(); - } catch (Exception e) - { + + letter = proc.StandardOutput.ReadLine(); } + + if (!Directory.Exists(letter + @"\sources\sxs") || !Directory.GetFiles(letter + @"\sources\sxs", "*netfx3*").Any()) + { + try + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.CreateNoWindow = false; + startInfo.UseShellExecute = false; + startInfo.FileName = "PowerShell.exe"; + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.Arguments = $"-NoP -C \"Dismount-DiskImage '{file}'\""; + startInfo.RedirectStandardOutput = true; + + var proc = Process.Start(startInfo); + proc.WaitForExit(); + } catch (Exception e) + { + } + Console.WriteLine(); + ConsoleTUI.OpenFrame.Close("ISO does not contain the required files.", ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt() { AnyKey = true, Text = "Press any key to return to the Menu: " }); + return false; + } + } + else + { Console.WriteLine(); - ConsoleTUI.OpenFrame.Close("ISO does not contain the required files.", ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt() { AnyKey = true, Text = "Press any key to return to the Menu: " }); - return false; + ConsoleTUI.OpenFrame.Close("\r\nYou must select an ISO.", new ChoicePrompt() {AnyKey = true, Text = "Press any key to return to the Menu: "}); + return true; } + } - ConsoleTUI.OpenFrame.WriteCentered("\r\nInstalling .NET 3.5"); - var topCache = Console.CursorTop; - var leftCache = Console.CursorLeft; - Console.WriteLine(); - bool inProgress = false; - try + ConsoleTUI.OpenFrame.WriteCentered("\r\nInstalling .NET 3.5"); + var topCache = Console.CursorTop; + var leftCache = Console.CursorLeft; + Console.WriteLine(); + bool inProgress = false; + try + { + using (var indicator = new ConsoleUtils.LoadingIndicator()) { - using (var indicator = new ConsoleUtils.LoadingIndicator()) + DismApi.Initialize(DismLogLevel.LogErrors); + using (var session = DismApi.OpenOnlineSession()) { - DismApi.Initialize(DismLogLevel.LogErrors); - using (var session = DismApi.OpenOnlineSession()) + var stdout = GetStdHandle(-11); + bool indicatorStopped = false; + var maxHashTags = (ConsoleTUI.OpenFrame.DisplayWidth - 5); + DismApi.EnableFeatureByPackagePath(session, "NetFX3", null, true, true, new List() { letter + @"\sources\sxs" }, delegate(DismProgress progress) { - var stdout = GetStdHandle(-11); - bool indicatorStopped = false; - var maxHashTags = (ConsoleTUI.OpenFrame.DisplayWidth - 5); - DismApi.EnableFeatureByPackagePath(session, "NetFX3", null, true, true, new List() { letter + @":\sources\sxs" }, delegate(DismProgress progress) + inProgress = true; + if (!indicatorStopped) { - inProgress = true; - if (!indicatorStopped) - { - indicator.Stop(); - Console.SetCursorPosition(leftCache, topCache); - Console.WriteLine(" "); - } - - indicatorStopped = true; - var progressPerc = progress.Current / 10; - var currentHashTags = (int)Math.Ceiling(Math.Min(((double)progressPerc / 100) * maxHashTags, maxHashTags)); - var spaces = maxHashTags - currentHashTags + (4 - progressPerc.ToString().Length); - var sb = new StringBuilder(new string('#', currentHashTags) + new string(' ', spaces) + progressPerc + "%"); - uint throwaway; - WriteConsoleOutputCharacter(stdout, sb, (uint)sb.Length, new Languages.COORD((short)ConsoleTUI.OpenFrame.DisplayOffset, (short)Console.CursorTop), out throwaway); - inProgress = false; - }); - session.Close(); - Thread.Sleep(100); - var sb = new StringBuilder(new string('#', maxHashTags) + " 100%"); + indicator.Stop(); + Console.SetCursorPosition(leftCache, topCache); + Console.WriteLine(" "); + } + + indicatorStopped = true; + var progressPerc = progress.Current / 10; + var currentHashTags = (int)Math.Ceiling(Math.Min(((double)progressPerc / 100) * maxHashTags, maxHashTags)); + var spaces = maxHashTags - currentHashTags + (4 - progressPerc.ToString().Length); + var sb = new StringBuilder(new string('#', currentHashTags) + new string(' ', spaces) + progressPerc + "%"); uint throwaway; WriteConsoleOutputCharacter(stdout, sb, (uint)sb.Length, new Languages.COORD((short)ConsoleTUI.OpenFrame.DisplayOffset, (short)Console.CursorTop), out throwaway); - } - - DismApi.Shutdown(); - - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.CreateNoWindow = false; - startInfo.UseShellExecute = false; - startInfo.FileName = "PowerShell.exe"; - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.Arguments = $"-NoP -C \"Dismount-DiskImage '{file}'\""; - startInfo.RedirectStandardOutput = true; - - var proc = Process.Start(startInfo); - proc.WaitForExit(); + inProgress = false; + }); + session.Close(); + Thread.Sleep(100); + var sb = new StringBuilder(new string('#', maxHashTags) + " 100%"); + uint throwaway; + WriteConsoleOutputCharacter(stdout, sb, (uint)sb.Length, new Languages.COORD((short)ConsoleTUI.OpenFrame.DisplayOffset, (short)Console.CursorTop), out throwaway); } - } catch (Exception e) + + DismApi.Shutdown(); + if (usingFolder) DismountImage(); + } + } catch (Exception e) + { + while (inProgress) { - while (inProgress) - { - Thread.Sleep(50); - } + Thread.Sleep(50); + } + if (usingFolder) + { try { - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.CreateNoWindow = false; - startInfo.UseShellExecute = false; - startInfo.FileName = "PowerShell.exe"; - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - startInfo.Arguments = $"-NoP -C \"Dismount-DiskImage '{file}'\""; - startInfo.RedirectStandardOutput = true; - - var proc = Process.Start(startInfo); - proc.WaitForExit(); - } catch (Exception ex) - { + DismountImage(); } - - Console.WriteLine(); - Console.WriteLine(); - ConsoleTUI.OpenFrame.Close("DISM error: " + e.Message, ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt() + catch (Exception ex) { - AnyKey = true, - Text = "Press any key to return to the Menu: " - }); - return false; + } } - + Console.WriteLine(); Console.WriteLine(); - ConsoleTUI.OpenFrame.Close(".NET 3.5 installed successfully", ConsoleColor.Green, Console.BackgroundColor, new ChoicePrompt() + ConsoleTUI.OpenFrame.Close("DISM error: " + e.Message, ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt() { AnyKey = true, Text = "Press any key to return to the Menu: " }); - return true; + return false; } - else + + Console.WriteLine(); + Console.WriteLine(); + ConsoleTUI.OpenFrame.Close(".NET 3.5 installed successfully", ConsoleColor.Green, Console.BackgroundColor, new ChoicePrompt() { - Console.WriteLine(); - ConsoleTUI.OpenFrame.Close("\r\nYou must select an ISO.", new ChoicePrompt() {AnyKey = true, Text = "Press any key to return to the Menu: "}); - return true; - } + AnyKey = true, + Text = "Press any key to return to the Menu: " + }); + return true; } [StructLayout(LayoutKind.Sequential)] diff --git a/src/Misc.FolderPicker/FolderPicker.cs b/src/Misc.FolderPicker/FolderPicker.cs new file mode 100644 index 0000000..1074d0e --- /dev/null +++ b/src/Misc.FolderPicker/FolderPicker.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using System.Windows.Interop; + +namespace Misc.FolderPicker +{ + public class FolderPicker + { + private readonly List _resultPaths = new List(); + private readonly List _resultNames = new List(); + + public IReadOnlyList ResultPaths => _resultPaths; + public IReadOnlyList ResultNames => _resultNames; + public string ResultPath => ResultPaths.FirstOrDefault(); + public string ResultName => ResultNames.FirstOrDefault(); + public virtual string InputPath { get; set; } + public virtual bool ForceFileSystem { get; set; } + public virtual bool Multiselect { get; set; } + public virtual string Title { get; set; } + public virtual string OkButtonLabel { get; set; } + public virtual string FileNameLabel { get; set; } + + protected virtual int SetOptions(int options) + { + if (ForceFileSystem) + { + options |= (int)FOS.FOS_FORCEFILESYSTEM; + } + + if (Multiselect) + { + options |= (int)FOS.FOS_ALLOWMULTISELECT; + } + return options; + } + + // for all .NET + public virtual bool? ShowDialog(IntPtr owner, bool throwOnError = false) + { + var dialog = (IFileOpenDialog)new FileOpenDialog(); + if (!string.IsNullOrEmpty(InputPath)) + { + if (CheckHr(SHCreateItemFromParsingName(InputPath, null, typeof(IShellItem).GUID, out var item), throwOnError) != 0) + return null; + + dialog.SetFolder(item); + } + + var options = FOS.FOS_PICKFOLDERS; + options = (FOS)SetOptions((int)options); + dialog.SetOptions(options); + + if (Title != null) + { + dialog.SetTitle(Title); + } + + if (OkButtonLabel != null) + { + dialog.SetOkButtonLabel(OkButtonLabel); + } + + if (FileNameLabel != null) + { + dialog.SetFileName(FileNameLabel); + } + + if (owner == IntPtr.Zero) + { + owner = Process.GetCurrentProcess().MainWindowHandle; + if (owner == IntPtr.Zero) + { + owner = GetDesktopWindow(); + } + } + + var hr = dialog.Show(owner); + if (hr == ERROR_CANCELLED) + return null; + + if (CheckHr(hr, throwOnError) != 0) + return null; + + if (CheckHr(dialog.GetResults(out var items), throwOnError) != 0) + return null; + + items.GetCount(out var count); + for (var i = 0; i < count; i++) + { + items.GetItemAt(i, out var item); + CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var path), throwOnError); + CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out var name), throwOnError); + if (path != null || name != null) + { + _resultPaths.Add(path); + _resultNames.Add(name); + } + } + return true; + } + + private static int CheckHr(int hr, bool throwOnError) + { + if (hr != 0 && throwOnError) Marshal.ThrowExceptionForHR(hr); + return hr; + } + + [DllImport("shell32")] + private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv); + + [DllImport("user32")] + private static extern IntPtr GetDesktopWindow(); + + #pragma warning disable IDE1006 // Naming Styles + private const int ERROR_CANCELLED = unchecked((int)0x800704C7); + #pragma warning restore IDE1006 // Naming Styles + + [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] // CLSID_FileOpenDialog + private class FileOpenDialog { } + + [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IFileOpenDialog + { + [PreserveSig] int Show(IntPtr parent); // IModalWindow + [PreserveSig] int SetFileTypes(); // not fully defined + [PreserveSig] int SetFileTypeIndex(int iFileType); + [PreserveSig] int GetFileTypeIndex(out int piFileType); + [PreserveSig] int Advise(); // not fully defined + [PreserveSig] int Unadvise(); + [PreserveSig] int SetOptions(FOS fos); + [PreserveSig] int GetOptions(out FOS pfos); + [PreserveSig] int SetDefaultFolder(IShellItem psi); + [PreserveSig] int SetFolder(IShellItem psi); + [PreserveSig] int GetFolder(out IShellItem ppsi); + [PreserveSig] int GetCurrentSelection(out IShellItem ppsi); + [PreserveSig] int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName); + [PreserveSig] int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + [PreserveSig] int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + [PreserveSig] int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText); + [PreserveSig] int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + [PreserveSig] int GetResult(out IShellItem ppsi); + [PreserveSig] int AddPlace(IShellItem psi, int alignment); + [PreserveSig] int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + [PreserveSig] int Close(int hr); + [PreserveSig] int SetClientGuid(); // not fully defined + [PreserveSig] int ClearClientData(); + [PreserveSig] int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter); + [PreserveSig] int GetResults(out IShellItemArray ppenum); + [PreserveSig] int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai); + } + + [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IShellItem + { + [PreserveSig] int BindToHandler(); // not fully defined + [PreserveSig] int GetParent(); // not fully defined + [PreserveSig] int GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + [PreserveSig] int GetAttributes(); // not fully defined + [PreserveSig] int Compare(); // not fully defined + } + + [ComImport, Guid("b63ea76d-1f85-456f-a19c-48159efa858b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IShellItemArray + { + [PreserveSig] int BindToHandler(); // not fully defined + [PreserveSig] int GetPropertyStore(); // not fully defined + [PreserveSig] int GetPropertyDescriptionList(); // not fully defined + [PreserveSig] int GetAttributes(); // not fully defined + [PreserveSig] int GetCount(out int pdwNumItems); + [PreserveSig] int GetItemAt(int dwIndex, out IShellItem ppsi); + [PreserveSig] int EnumItems(); // not fully defined + } + + #pragma warning disable CA1712 // Do not prefix enum values with type name + private enum SIGDN : uint + { + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, + SIGDN_FILESYSPATH = 0x80058000, + SIGDN_NORMALDISPLAY = 0, + SIGDN_PARENTRELATIVE = 0x80080001, + SIGDN_PARENTRELATIVEEDITING = 0x80031001, + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, + SIGDN_PARENTRELATIVEPARSING = 0x80018001, + SIGDN_URL = 0x80068000 + } + + [Flags] + private enum FOS + { + FOS_OVERWRITEPROMPT = 0x2, + FOS_STRICTFILETYPES = 0x4, + FOS_NOCHANGEDIR = 0x8, + FOS_PICKFOLDERS = 0x20, + FOS_FORCEFILESYSTEM = 0x40, + FOS_ALLNONSTORAGEITEMS = 0x80, + FOS_NOVALIDATE = 0x100, + FOS_ALLOWMULTISELECT = 0x200, + FOS_PATHMUSTEXIST = 0x800, + FOS_FILEMUSTEXIST = 0x1000, + FOS_CREATEPROMPT = 0x2000, + FOS_SHAREAWARE = 0x4000, + FOS_NOREADONLYRETURN = 0x8000, + FOS_NOTESTFILECREATE = 0x10000, + FOS_HIDEMRUPLACES = 0x20000, + FOS_HIDEPINNEDPLACES = 0x40000, + FOS_NODEREFERENCELINKS = 0x100000, + FOS_OKBUTTONNEEDSINTERACTION = 0x200000, + FOS_DONTADDTORECENT = 0x2000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000, + FOS_FORCEPREVIEWPANEON = 0x40000000, + FOS_SUPPORTSTREAMABLEITEMS = unchecked((int)0x80000000) + } + #pragma warning restore CA1712 // Do not prefix enum values with type name + } +} \ No newline at end of file diff --git a/src/amecs.csproj b/src/amecs.csproj index ee04ded..cfac80e 100644 --- a/src/amecs.csproj +++ b/src/amecs.csproj @@ -197,6 +197,7 @@ +