Add support for quiet setup

This commit is contained in:
Lauri Kenttä
2023-09-10 15:02:58 +03:00
parent 990f245ac9
commit 31172f71ca
3 changed files with 188 additions and 87 deletions
+17 -8
View File
@@ -23,21 +23,30 @@ When booting on a UEFI-based computer, Windows may show a vendor-defined logo wh
* If you wish to change the image or other configuration, just reinstall. * If you wish to change the image or other configuration, just reinstall.
* For advanced settings, edit `config.txt` before installing. No extra support provided! * For advanced settings, edit `config.txt` before installing. No extra support provided!
### Quiet (batch) installation
* Edit the `config.txt` and `splash.bmp` (or any other images) to your needs.
* Run `setup.exe batch COMMANDS` as administrator, with some of the following commands:
* `install` – copy the files but don't enable.
* `enable-overwrite` – overwrite the MS boot loader.
* `disable-overwrite` – restore the MS boot loader.
* `allow-secure-boot` – ignore Secure Boot in subsequent commands.
* `disable` – run all relevant `disable-*` commands.
* `uninstall` – disable and remove completely.
* For example, run `setup.exe batch install allow-secure-boot enable-overwrite` to copy files and overwrite the MS boot loader regardless of Secure Boot status.
### Multi-boot configurations ### Multi-boot configurations
If you only need HackBGRT for Windows: If you only need HackBGRT for Windows:
* Extract the latest release to `[EFI System Partition]\EFI\HackBGRT\`. * Run `setup.exe`, install files without enabling.
* Set `boot=\EFI\Microsoft\Boot\bootmgfw.efi` in `config.txt`. * Configure your boot loader to start `\EFI\HackBGRT\loader.efi`.
* Point your boot loader to `\EFI\HackBGRT\bootx64.efi`.
If you need it for other systems as well: If you need it for other systems as well:
* Extract the latest release to `[EFI System Partition]\EFI\HackBGRT\`. * Configure HackBGRT to start your boot loader (such as systemd-boot): `boot=\EFI\systemd\systemd-bootx64.efi`.
* Set `boot=\EFI\your-actual-boot-loader.efi` in `config.txt`. * Run `setup.exe`, install files.
* Set `\EFI\HackBGRT\bootx64.efi` as your default boot loader with `efibootmgr` or some other EFI boot manager tool. * Set `\EFI\HackBGRT\loader.efi` as your default boot loader with `efibootmgr` or some other EFI boot manager tool.
On 32-bit machines, use `bootia32.efi` instead of `bootx64.efi`.
## Configuration ## Configuration
+145 -49
View File
@@ -3,11 +3,22 @@ using System.IO;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Collections.Generic;
/** /**
* HackBGRT Setup. * HackBGRT Setup.
*/ */
public class Setup: SetupHelper { public class Setup: SetupHelper {
/** @var Version of the setup program. */
const string Version =
#if GIT_DESCRIBE
GIT_DESCRIBE.data
#else
"unknown; not an official release?"
#endif
;
/** /**
* The custom exception class for expected exceptions. * The custom exception class for expected exceptions.
*/ */
@@ -26,6 +37,15 @@ public class Setup: SetupHelper {
} }
} }
/** @var The privileged actions. */
protected static readonly string[] privilegedActions = new string[] {
"install",
"allow-secure-boot",
"enable-overwrite", "disable-overwrite",
"disable",
"uninstall",
};
/** @var The target directory. */ /** @var The target directory. */
protected string InstallPath; protected string InstallPath;
@@ -36,11 +56,14 @@ public class Setup: SetupHelper {
} }
} }
/** @var Run in batch mode? */
protected bool Batch;
/** /**
* Find or mount or manually choose the EFI System Partition. * Find or mount or manually choose the EFI System Partition.
*/ */
public static void InitEspPath() { protected void InitEspPath() {
if (!Esp.Find() && !Esp.Mount()) { if (Esp.Location == null && !Esp.Find() && !Esp.Mount() && !Batch) {
Console.WriteLine("EFI System Partition was not found."); Console.WriteLine("EFI System Partition was not found.");
Console.WriteLine("Press enter to exit, or give ESP path here: "); Console.WriteLine("Press enter to exit, or give ESP path here: ");
string s = Console.ReadLine(); string s = Console.ReadLine();
@@ -165,7 +188,7 @@ public class Setup: SetupHelper {
/** /**
* Install files to ESP. * Install files to ESP.
*/ */
protected void Install() { protected void InstallFiles() {
try { try {
if (!Directory.Exists(InstallPath)) { if (!Directory.Exists(InstallPath)) {
Directory.CreateDirectory(InstallPath); Directory.CreateDirectory(InstallPath);
@@ -182,9 +205,6 @@ public class Setup: SetupHelper {
// Duplicate check, but better to be sure... // Duplicate check, but better to be sure...
throw new SetupException("Failed to detect MS boot loader!"); throw new SetupException("Failed to detect MS boot loader!");
} }
if (!NewLoader.ReplaceWith(NewLoaderSource)) {
throw new SetupException("Couldn't copy new HackBGRT to ESP.");
}
InstallFile("config.txt"); InstallFile("config.txt");
foreach (var line in File.ReadAllLines("config.txt").Where(s => s.StartsWith("image="))) { foreach (var line in File.ReadAllLines("config.txt").Where(s => s.StartsWith("image="))) {
var delim = "path=\\EFI\\HackBGRT\\"; var delim = "path=\\EFI\\HackBGRT\\";
@@ -193,11 +213,32 @@ public class Setup: SetupHelper {
InstallImageFile(line.Substring(i + delim.Length)); InstallImageFile(line.Substring(i + delim.Length));
} }
} }
InstallFile($"boot{EfiArch}.efi", "loader.efi");
Console.WriteLine($"HackBGRT has been copied to {InstallPath}.");
}
/**
* Enable HackBGRT by overwriting the MS boot loader.
*/
protected void OverwriteMsLoader() {
var NewLoader = new BootLoaderInfo(Path.Combine(InstallPath, "loader.efi"));
if (!MsLoader.ReplaceWith(NewLoader)) { if (!MsLoader.ReplaceWith(NewLoader)) {
MsLoader.ReplaceWith(MsLoaderBackup); MsLoader.ReplaceWith(MsLoaderBackup);
throw new SetupException("Couldn't copy new HackBGRT over the MS loader (bootmgfw.efi)."); throw new SetupException("Couldn't copy new HackBGRT over the MS loader (bootmgfw.efi).");
} }
Console.WriteLine("HackBGRT is now installed."); Console.WriteLine($"Replaced {MsLoader.Path} with this app, stored backup in {MsLoaderBackup.Path}.");
}
/**
* Restore the MS boot loader if it was previously replaced.
*/
protected void RestoreMsLoader() {
if (MsLoader.Type != BootLoaderType.MS) {
if (!MsLoader.ReplaceWith(MsLoaderBackup)) {
throw new SetupException("Couldn't restore the old MS loader.");
}
Console.WriteLine($"{MsLoader.Path} has been restored.");
}
} }
/** /**
@@ -220,22 +261,11 @@ public class Setup: SetupHelper {
Console.WriteLine(); Console.WriteLine();
} }
/**
* Disable HackBGRT, restore the original boot loader.
*/
protected void Disable() {
if (MsLoader.Type != BootLoaderType.MS) {
if (!MsLoader.ReplaceWith(MsLoaderBackup)) {
throw new SetupException("Couldn't restore the old MS loader.");
}
}
Console.WriteLine(MsLoader.Path + " has been restored.");
}
/** /**
* Uninstall HackBGRT completely. * Uninstall HackBGRT completely.
*/ */
protected void Uninstall() { protected void Uninstall() {
RestoreMsLoader();
try { try {
Directory.Delete(InstallPath, true); Directory.Delete(InstallPath, true);
Console.WriteLine("HackBGRT has been removed."); Console.WriteLine("HackBGRT has been removed.");
@@ -246,8 +276,10 @@ public class Setup: SetupHelper {
/** /**
* Check Secure Boot status and inform the user. * Check Secure Boot status and inform the user.
*
* @param allowSecureBoot Allow Secure Boot to be enabled?
*/ */
public static void HandleSecureBoot() { protected void HandleSecureBoot(bool allowSecureBoot) {
int secureBoot = Efi.GetSecureBootStatus(); int secureBoot = Efi.GetSecureBootStatus();
if (secureBoot == 0) { if (secureBoot == 0) {
Console.WriteLine("Secure Boot is disabled, good!"); Console.WriteLine("Secure Boot is disabled, good!");
@@ -259,6 +291,12 @@ public class Setup: SetupHelper {
} }
Console.WriteLine("It's very important to disable Secure Boot before installing."); Console.WriteLine("It's very important to disable Secure Boot before installing.");
Console.WriteLine("Otherwise your machine may become unbootable."); Console.WriteLine("Otherwise your machine may become unbootable.");
if (Batch) {
if (allowSecureBoot) {
return;
}
throw new SetupException("Aborting because of Secure Boot.");
}
Console.WriteLine("Choose action (press a key):"); Console.WriteLine("Choose action (press a key):");
bool canBootToFW = Efi.CanBootToFW(); bool canBootToFW = Efi.CanBootToFW();
if (canBootToFW) { if (canBootToFW) {
@@ -281,22 +319,20 @@ public class Setup: SetupHelper {
if (k2 == ConsoleKey.R) { if (k2 == ConsoleKey.R) {
StartProcess("shutdown", "-f -r -t 1"); StartProcess("shutdown", "-f -r -t 1");
} }
Environment.Exit(0);
throw new ExitSetup(0); throw new ExitSetup(0);
} else { } else {
Console.WriteLine("Aborting because of Secure Boot."); throw new SetupException("Aborting because of Secure Boot.");
throw new ExitSetup(1);
} }
} }
} }
protected BootLoaderInfo MsLoader, MsLoaderBackup, NewLoader, NewLoaderSource; protected BootLoaderInfo MsLoader, MsLoaderBackup;
protected string EfiArch; protected string EfiArch;
/** /**
* Initialize information for the Setup. * Initialize information for the Setup.
*/ */
protected Setup() { protected void InitEspInfo() {
InstallPath = Path.Combine(Esp.Location, "EFI", "HackBGRT"); InstallPath = Path.Combine(Esp.Location, "EFI", "HackBGRT");
MsLoaderBackup = new BootLoaderInfo(BackupLoaderPath); MsLoaderBackup = new BootLoaderInfo(BackupLoaderPath);
MsLoader = new BootLoaderInfo(Esp.MsLoaderPath); MsLoader = new BootLoaderInfo(Esp.MsLoaderPath);
@@ -307,18 +343,13 @@ public class Setup: SetupHelper {
} else { } else {
throw new SetupException("Failed to detect MS boot loader!"); throw new SetupException("Failed to detect MS boot loader!");
} }
string loaderName = "boot" + EfiArch + ".efi";
NewLoaderSource = new BootLoaderInfo(loaderName);
NewLoader = new BootLoaderInfo(Path.Combine(InstallPath, loaderName));
if (!NewLoaderSource.Exists) {
throw new SetupException("Couldn't find required files! Missing: " + NewLoaderSource.Path);
}
} }
/** /**
* Ask for user's choice and install/uninstall. * Ask for user's choice and install/uninstall.
*/ */
protected void HandleUserAction() { protected void ShowMenu() {
var NewLoader = new BootLoaderInfo(Path.Combine(InstallPath, "loader.efi"));
bool isEnabled = MsLoader.Type == BootLoaderType.Own; bool isEnabled = MsLoader.Type == BootLoaderType.Own;
bool isInstalled = NewLoader.Type == BootLoaderType.Own; bool isInstalled = NewLoader.Type == BootLoaderType.Own;
@@ -334,6 +365,7 @@ public class Setup: SetupHelper {
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Choose action (press a key):"); Console.WriteLine("Choose action (press a key):");
Console.WriteLine(" I = install, upgrade, repair, modify..."); Console.WriteLine(" I = install, upgrade, repair, modify...");
Console.WriteLine(" F = install files only, don't enable");
if (isEnabled) { if (isEnabled) {
Console.WriteLine(" D = disable, restore the original boot loader"); Console.WriteLine(" D = disable, restore the original boot loader");
} }
@@ -344,16 +376,17 @@ public class Setup: SetupHelper {
var k = Console.ReadKey().Key; var k = Console.ReadKey().Key;
Console.WriteLine(); Console.WriteLine();
if (k == ConsoleKey.I) { if (k == ConsoleKey.I || k == ConsoleKey.F) {
Configure(); Configure();
Install();
} else if ((isEnabled && k == ConsoleKey.D) || (isInstalled && k == ConsoleKey.R)) {
if (isEnabled) {
Disable();
}
if (k == ConsoleKey.R) {
Uninstall(); // NOTICE: It's imperative to Disable() first!
} }
if (k == ConsoleKey.I) {
RunPrivilegedActions(new string[] { "install", "enable-overwrite" });
} else if (k == ConsoleKey.F) {
RunPrivilegedActions(new string[] { "install" });
} else if (k == ConsoleKey.D) {
RunPrivilegedActions(new string[] { "disable" });
} else if (k == ConsoleKey.R) {
RunPrivilegedActions(new string[] { "uninstall" });
} else if (k == ConsoleKey.C) { } else if (k == ConsoleKey.C) {
throw new ExitSetup(1); throw new ExitSetup(1);
} else { } else {
@@ -362,25 +395,88 @@ public class Setup: SetupHelper {
} }
/** /**
* Run the setup. * Run privileged actions.
*
* @param actions The actions to run.
*/ */
public static void RunSetup() { protected void RunPrivilegedActions(IEnumerable<string> actions) {
bool allowSecureBoot = false;
foreach (var arg in actions) {
Log($"Running action '{arg}'.");
if (arg == "install") {
InstallFiles();
} else if (arg == "allow-secure-boot") {
allowSecureBoot = true;
} else if (arg == "enable-overwrite") {
HandleSecureBoot(allowSecureBoot);
OverwriteMsLoader();
} else if (arg == "disable-overwrite") {
RestoreMsLoader();
} else if (arg == "disable") {
RestoreMsLoader();
} else if (arg == "uninstall") {
Uninstall();
} else {
throw new SetupException($"Invalid action: '{arg}'!");
}
Console.WriteLine($"Completed action '{arg}' successfully.");
}
}
/**
* The Main program.
*
* @param args The arguments.
*/
public static void Main(string[] args) {
Console.WriteLine($"HackBGRT installer version: {Version}");
var self = Assembly.GetExecutingAssembly().Location;
Directory.SetCurrentDirectory(Path.GetDirectoryName(self));
Environment.ExitCode = new Setup().Run(args);
}
/**
* Run the setup.
*
* @param args The arguments.
*/
protected int Run(string[] args) {
Batch = args.Contains("batch");
try { try {
if (args.Contains("is-elevated") && !HasPrivileges()) {
Console.WriteLine("This installer needs to be run as administrator!");
return 1;
}
if (!HasPrivileges()) {
var self = Assembly.GetExecutingAssembly().Location;
return RunElevated(self, String.Join(" ", args.Prepend("is-elevated")));
}
InitEspPath(); InitEspPath();
HandleSecureBoot(); InitEspInfo();
var s = new Setup(); var actions = args.Where(s => privilegedActions.Contains(s));
s.HandleUserAction(); if (actions.Count() > 0) {
RunPrivilegedActions(actions);
return 0;
}
if (Batch) {
throw new SetupException("No action specified!");
}
ShowMenu();
return 0;
} catch (ExitSetup e) { } catch (ExitSetup e) {
Environment.ExitCode = e.Code; return e.Code;
} catch (SetupException e) { } catch (SetupException e) {
Console.WriteLine("Error: {0}", e.Message); Console.WriteLine("Error: {0}", e.Message);
Environment.ExitCode = 1; return 1;
} catch (Exception e) { } catch (Exception e) {
Console.WriteLine("Unexpected error!\n{0}", e.ToString()); Console.WriteLine("Unexpected error!\n{0}", e.ToString());
Console.WriteLine("If this is the most current release, please report this bug."); Console.WriteLine("If this is the most current release, please report this bug.");
Environment.ExitCode = 1; return 1;
} } finally {
if (!Batch) {
Console.WriteLine("Press any key to quit."); Console.WriteLine("Press any key to quit.");
Console.ReadKey(); Console.ReadKey();
} }
}
}
} }
+24 -28
View File
@@ -1,9 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection;
using System.Security.Principal; using System.Security.Principal;
using System.Linq;
/** /**
* Helper functions. * Helper functions.
@@ -65,41 +63,39 @@ public class SetupHelper {
} }
/** /**
* The Main program. * Check for required privileges (access to ESP and EFI vars).
* *
* @param args The arguments. * @return True, if the file was successfully copied.
*/ */
public static void Main(string[] args) { public static bool HasPrivileges() {
#if GIT_DESCRIBE
Console.WriteLine("HackBGRT installer version: {0}", GIT_DESCRIBE.data);
#else
Console.WriteLine("HackBGRT installer version: unknown; not an official release?");
#endif
var self = Assembly.GetExecutingAssembly().Location;
try { try {
// Relaunch as admin, if needed.
var id = WindowsIdentity.GetCurrent(); var id = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(id); var principal = new WindowsPrincipal(id);
var admin = WindowsBuiltInRole.Administrator; var admin = WindowsBuiltInRole.Administrator;
if (!principal.IsInRole(admin) && !args.Contains("no-elevate")) { // FIXME: Check access to ESP as well.
ProcessStartInfo startInfo = new ProcessStartInfo(self); if (!principal.IsInRole(admin)) {
startInfo.Arguments = "no-elevate"; return false;
}
Efi.EnablePrivilege();
return true;
} catch {
}
return false;
}
/**
* Run another process as admin, return the exit code.
*
* @param app Path to the application.
* @param args The argument string.
*/
public static int RunElevated(string app, string args) {
ProcessStartInfo startInfo = new ProcessStartInfo(app);
startInfo.Arguments = args;
startInfo.Verb = "runas"; startInfo.Verb = "runas";
startInfo.UseShellExecute = true; startInfo.UseShellExecute = true;
Process p = Process.Start(startInfo); Process p = Process.Start(startInfo);
p.WaitForExit(); p.WaitForExit();
Environment.ExitCode = p.ExitCode; return p.ExitCode;
return;
}
Efi.EnablePrivilege();
} catch {
Console.WriteLine("This installer needs to be run as administrator!");
Console.WriteLine("Press any key to quit.");
Console.ReadKey();
Environment.ExitCode = 1;
return;
}
Directory.SetCurrentDirectory(Path.GetDirectoryName(self));
Setup.RunSetup();
} }
} }