From d0d9ec486f830e482a79d4dbd580cacb26115366 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Sat, 10 May 2025 16:14:38 +0100
Subject: [PATCH 01/30] Refactor code (#35)
* WindowsInstallerLib: refactor DeployManager class
Rewrite some functions on this class to make it easier for understanding the code and thus optimizing it.
* WindowsInstallerLib: refactor InstallerManager
Rewrite some functions on this class to make it easier for understanding the code and thus optimizing it.
---
WindowsInstallerLib/src/DeployManager.cs | 72 ++++++++++++---------
WindowsInstallerLib/src/InstallerManager.cs | 54 +++++++++-------
2 files changed, 72 insertions(+), 54 deletions(-)
diff --git a/WindowsInstallerLib/src/DeployManager.cs b/WindowsInstallerLib/src/DeployManager.cs
index 684f09e..e305604 100644
--- a/WindowsInstallerLib/src/DeployManager.cs
+++ b/WindowsInstallerLib/src/DeployManager.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Runtime.Versioning;
+using System.Threading;
using Microsoft.Dism;
namespace WindowsInstallerLib
@@ -94,20 +95,24 @@ internal static string GetImageFile(ref Parameters parameters)
ArgumentException.ThrowIfNullOrWhiteSpace(nameof(parameters.SourceDrive));
ArgumentException.ThrowIfNullOrWhiteSpace(nameof(parameters.ImageFilePath));
- if (File.Exists(@$"{parameters.SourceDrive}\sources\install.esd"))
+ string IMAGE_FILE_ESD = Path.Join(parameters.SourceDrive, @"\sources\install.esd");
+ string IMAGE_FILE_WIM = Path.Join(parameters.SourceDrive, @"\sources\install.wim");
+
+ bool IS_IMAGE_FILE_ESD = File.Exists(IMAGE_FILE_ESD);
+ bool IS_IMAGE_FILE_WIM = File.Exists(IMAGE_FILE_WIM);
+
+ if (IS_IMAGE_FILE_ESD)
{
- parameters.ImageFilePath = @$"{parameters.SourceDrive}\sources\install.esd";
+ return IMAGE_FILE_ESD;
}
- else if (File.Exists(@$"{parameters.SourceDrive}\sources\install.wim"))
+ else if (IS_IMAGE_FILE_WIM)
{
- parameters.ImageFilePath = @$"{parameters.SourceDrive}\sources\install.wim";
+ return IMAGE_FILE_WIM;
}
else
{
throw new FileNotFoundException($"Could not find a valid image file at {parameters.SourceDrive}.");
}
-
- return parameters.ImageFilePath;
}
catch (Exception)
{
@@ -176,22 +181,23 @@ internal static void GetImageInfo(ref Parameters parameters)
///
internal static DismImageInfoCollection GetImageInfoT(ref Parameters parameters)
{
- try
+ if (string.IsNullOrEmpty(parameters.ImageFilePath) ||
+ string.IsNullOrWhiteSpace(parameters.ImageFilePath))
{
- if (string.IsNullOrEmpty(parameters.ImageFilePath))
- {
- throw new FileNotFoundException("No image file was specified.", parameters.ImageFilePath);
- }
+ throw new FileNotFoundException("No image file was specified.", parameters.ImageFilePath);
+ }
- switch (PrivilegesManager.IsAdmin())
- {
- case true:
- DismApi.Initialize(DismLogLevel.LogErrorsWarnings);
- break;
- case false:
- throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
- }
+ switch (PrivilegesManager.IsAdmin())
+ {
+ case true:
+ DismApi.Initialize(DismLogLevel.LogErrorsWarnings);
+ break;
+ case false:
+ throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
+ }
+ try
+ {
DismImageInfoCollection images = DismApi.GetImageInfo(parameters.ImageFilePath);
return images;
@@ -213,7 +219,7 @@ internal static DismImageInfoCollection GetImageInfoT(ref Parameters parameters)
///
/// Installs the bootloader to the EFI drive of a new Windows installation.
///
- ///
+ ///
internal static int InstallBootloader(ref Parameters parameters)
{
@@ -221,6 +227,14 @@ internal static int InstallBootloader(ref Parameters parameters)
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.EfiDrive, nameof(parameters.EfiDrive));
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.FirmwareType, nameof(parameters.FirmwareType));
+ string EFI_BOOT_PATH = Path.Join(parameters.EfiDrive, @"\EFI\Boot");
+ string EFI_MICROSOFT_PATH = Path.Join(parameters.EfiDrive, @"\EFI\Microsoft");
+ string WINDIR_PATH = Path.Join(parameters.DestinationDrive, @"\windows");
+
+ bool EFI_BOOT_EXISTS = Directory.Exists(EFI_BOOT_PATH);
+ bool EFI_MICROSOFT_EXISTS = Directory.Exists(EFI_MICROSOFT_PATH);
+ bool WINDIR_EXISTS = Directory.Exists(WINDIR_PATH);
+
if (!PrivilegesManager.IsAdmin())
{
throw new UnauthorizedAccessException($"You do not have enough privileges to install the bootloader to {parameters.EfiDrive}");
@@ -228,21 +242,19 @@ internal static int InstallBootloader(ref Parameters parameters)
try
{
- if (Directory.Exists(@$"{parameters.EfiDrive}\EFI\Boot") || Directory.Exists($@"{parameters.EfiDrive}\EFI\Microsoft"))
+ if (EFI_BOOT_EXISTS || EFI_MICROSOFT_EXISTS)
{
throw new IOException($"The drive letter {parameters.EfiDrive} is already in use.");
}
- else
+
+ if (!WINDIR_EXISTS)
{
- if (!Directory.Exists(@$"{parameters.DestinationDrive}windows"))
- {
- throw new DirectoryNotFoundException(@$"The directory {parameters.DestinationDrive}windows does not exist!");
- }
-
- Console.WriteLine($"Firmware type is set to: {parameters.FirmwareType}");
- Console.WriteLine($"\n==> Installing bootloader to drive {parameters.EfiDrive} in disk {parameters.DiskNumber}");
- ProcessManager.StartCmdProcess("bcdboot", @$"{parameters.DestinationDrive}\windows /s {parameters.EfiDrive} /f {parameters.FirmwareType}");
+ throw new DirectoryNotFoundException(@$"The directory {WINDIR_PATH} does not exist!");
}
+
+ Console.WriteLine($"Firmware type is set to: {parameters.FirmwareType}");
+ Console.WriteLine($"\n==> Installing bootloader to drive {parameters.EfiDrive} in disk {parameters.DiskNumber}");
+ ProcessManager.StartCmdProcess("bcdboot", @$"{WINDIR_PATH} /s {parameters.EfiDrive} /f {parameters.FirmwareType}");
}
catch (IOException)
{
diff --git a/WindowsInstallerLib/src/InstallerManager.cs b/WindowsInstallerLib/src/InstallerManager.cs
index 623d673..b7221b2 100644
--- a/WindowsInstallerLib/src/InstallerManager.cs
+++ b/WindowsInstallerLib/src/InstallerManager.cs
@@ -269,7 +269,7 @@ public static void Configure(ref Parameters parameters)
Console.WriteLine($"\nThe installer has set the firmware type to {parameters.FirmwareType}.", ConsoleColor.Yellow);
break;
default:
- throw new InvalidDataException(nameof(parameters.FirmwareType));
+ throw new InvalidDataException($"Invalid firmware type: {parameters.FirmwareType}");
}
}
#endregion
@@ -282,35 +282,41 @@ public static void Configure(ref Parameters parameters)
[SupportedOSPlatform("windows")]
public static void InstallWindows(ref Parameters parameters)
{
- try
+ if (parameters.DiskNumber.Equals(-1))
{
- if (parameters.DiskNumber.Equals(-1))
- {
- throw new InvalidDataException("No disk number was specified, required to know where to install Windows at.");
- }
-
- if (string.IsNullOrWhiteSpace(parameters.EfiDrive))
- {
- throw new InvalidDataException("No EFI drive was specified, required for the bootloader installation.");
- }
+ throw new InvalidDataException("No disk number was specified, required to know where to install Windows at.");
+ }
- ArgumentException.ThrowIfNullOrWhiteSpace(parameters.DestinationDrive);
- ArgumentException.ThrowIfNullOrWhiteSpace(parameters.ImageFilePath);
- ArgumentOutOfRangeException.ThrowIfEqual(parameters.ImageIndex, -1);
- ArgumentException.ThrowIfNullOrWhiteSpace(parameters.FirmwareType);
+ if (string.IsNullOrWhiteSpace(parameters.EfiDrive))
+ {
+ throw new InvalidDataException("No EFI drive was specified, required for the bootloader installation.");
+ }
- switch (parameters.FirmwareType)
- {
- case "UEFI":
- break;
- case "BIOS":
- break;
- default:
- throw new InvalidDataException($"Invalid firmware type: {parameters.FirmwareType}");
- }
+ ArgumentException.ThrowIfNullOrWhiteSpace(parameters.DestinationDrive);
+ ArgumentException.ThrowIfNullOrWhiteSpace(parameters.ImageFilePath);
+ ArgumentOutOfRangeException.ThrowIfEqual(parameters.ImageIndex, -1);
+ ArgumentException.ThrowIfNullOrWhiteSpace(parameters.FirmwareType);
+ try
+ {
DiskManager.FormatDisk(ref parameters);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+
+ try
+ {
DeployManager.ApplyImage(ref parameters);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+
+ try
+ {
DeployManager.InstallBootloader(ref parameters);
}
catch (Exception)
From 899e72be44119fceb578d0aa7fea5b4bf5668a24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Tue, 20 May 2025 13:54:55 +0100
Subject: [PATCH 02/30] scripts: Refactor build scripts (#37)
The build system should work much better with this commit.
Error checking and files and folders are handled properly.
---
scripts/build-clean.bat | 4 ++--
scripts/build.bat | 2 +-
scripts/cleanup.bat | 32 +++++++++++++++++++-------------
scripts/publish-cleanup.bat | 8 ++++++--
scripts/publish.bat | 2 +-
5 files changed, 29 insertions(+), 19 deletions(-)
diff --git a/scripts/build-clean.bat b/scripts/build-clean.bat
index d60b3d7..ae4752e 100644
--- a/scripts/build-clean.bat
+++ b/scripts/build-clean.bat
@@ -1,5 +1,5 @@
@ECHO off
-START "WCIT CLEANUP SCRIPT" /B scripts/cleanup.bat
+CALL %~dp0/cleanup.bat
-dotnet build "Windows Installer.sln" --nologo --self-contained --property:OutputPath=..\build\ --configuration "Debug"
+dotnet build "Windows Installer.sln" --nologo --self-contained --property:OutputPath=%~dp0..\build\ --configuration "Debug"
diff --git a/scripts/build.bat b/scripts/build.bat
index 2a85041..6b8be71 100644
--- a/scripts/build.bat
+++ b/scripts/build.bat
@@ -1,3 +1,3 @@
@ECHO off
-dotnet build "Windows Installer.sln" --nologo --self-contained --property:OutputPath=..\build\ --configuration "Debug"
+dotnet build "Windows Installer.sln" --nologo --self-contained --property:OutputPath=%~dp0..\build\ --configuration "Debug"
diff --git a/scripts/cleanup.bat b/scripts/cleanup.bat
index 4ade831..c28821d 100644
--- a/scripts/cleanup.bat
+++ b/scripts/cleanup.bat
@@ -1,19 +1,25 @@
-@ECHO off
+@ECHO OFF
-IF EXIST "BUILD" (
- DEL /S /Q "BUILD"
-)
-IF EXIST "CONSOLEAPP\BIN" (
- DEL /S /Q "CONSOLEAPP\BIN"
-)
-
-IF EXIST "CONSOLEAPP\OBJ" (
- DEL /S /Q "CONSOLEAPP\OBJ"
-)
+CALL :REMOVEDIR %~dp0..\.vs
+CALL :REMOVEDIR %~dp0..\build
+CALL :REMOVEDIR %~dp0..\ConsoleApp\bin
+CALL :REMOVEDIR %~dp0..\ConsoleApp\obj
+CALL :REMOVEDIR %~dp0..\WindowsInstallerLib\bin
+CALL :REMOVEDIR %~dp0..\WindowsInstallerLib\obj
+:EXITWITHERROR
IF %ERRORLEVEL%==0 (
- EXIT
+ EXIT /B
) ELSE (
- ECHO.PROGRAM EXITED WITH CODE %ERRORLEVEL%
+ ECHO PROGRAM EXITED WITH CODE %ERRORLEVEL%
EXIT /B %ERRORLEVEL%
)
+
+:REMOVEDIR
+ IF EXIST %* (
+ ECHO REMOVING %* DIRECTORY...
+ RMDIR /S /Q %*
+ CALL :EXITWITHERROR
+ ) ELSE (
+ ECHO %* DIRECTORY NOT FOUND
+ )
diff --git a/scripts/publish-cleanup.bat b/scripts/publish-cleanup.bat
index f56b957..1b3d04b 100644
--- a/scripts/publish-cleanup.bat
+++ b/scripts/publish-cleanup.bat
@@ -1,5 +1,9 @@
@ECHO off
-START "WCIT CLEANUP SCRIPT" /B scripts/cleanup.bat
+CALL %~dp0cleanup.bat
-dotnet publish "Windows Installer.sln" --nologo --self-contained --property:OutputPath=..\build\ --configuration "Release"
+dotnet publish "Windows Installer.sln" --nologo --self-contained --property:OutputPath=%~dp0..\build\ --configuration "Release"
+
+MOVE /Y %~dp0..\build\publish %~dp0..\
+RMDIR /Q /S %~dp0..\build
+MOVE /Y %~dp0..\publish %~dp0..\build
diff --git a/scripts/publish.bat b/scripts/publish.bat
index 06b6fd3..3cefdf3 100644
--- a/scripts/publish.bat
+++ b/scripts/publish.bat
@@ -1,3 +1,3 @@
@ECHO off
-dotnet publish "Windows Installer.sln" --nologo --self-contained --property:OutputPath=..\build\ --configuration "Release"
+dotnet publish "Windows Installer.sln" --nologo --self-contained --property:OutputPath=%~dp0..\build\ --configuration "Release"
From 00a2263760f5b46815f73bf6f0edd9fa33305aa2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Tue, 20 May 2025 13:55:38 +0100
Subject: [PATCH 03/30] WindowsInstallerLib: Update functions documentation
(#38)
This commit makes the documentation of the library functions way better.
---
WindowsInstallerLib/src/DeployManager.cs | 82 +++++++++++++++-----
WindowsInstallerLib/src/DiskManager.cs | 16 +++-
WindowsInstallerLib/src/InstallerManager.cs | 52 +++++++++----
WindowsInstallerLib/src/PrivilegesManager.cs | 13 +++-
WindowsInstallerLib/src/ProcessManager.cs | 60 +++++++++++++-
WindowsInstallerLib/src/SystemInfoManager.cs | 26 +++++--
6 files changed, 197 insertions(+), 52 deletions(-)
diff --git a/WindowsInstallerLib/src/DeployManager.cs b/WindowsInstallerLib/src/DeployManager.cs
index e305604..b4d86b1 100644
--- a/WindowsInstallerLib/src/DeployManager.cs
+++ b/WindowsInstallerLib/src/DeployManager.cs
@@ -7,18 +7,29 @@
namespace WindowsInstallerLib
{
///
- /// Manages the deployment of Windows to a new drive.
+ /// Provides methods for managing the deployment of Windows images, including adding drivers, applying images,
+ /// retrieving image information, and installing the bootloader.
///
+ /// This class is designed to work with Windows Deployment Image Servicing and Management (DISM)
+ /// APIs and requires administrative privileges for most operations. It is supported only on Windows
+ /// platforms.
[SupportedOSPlatform("windows")]
internal static class DeployManager
{
///
- /// Adds drivers to the Windows image.
+ /// Adds one or more drivers to the specified offline Windows image.
///
- ///
- ///
- ///
- ///
+ /// This method initializes the DISM API, opens an offline session for the specified
+ /// destination drive, and adds the provided drivers. If is an array, all
+ /// drivers in the array are added recursively. Otherwise, a single driver is added. The DISM API is properly
+ /// shut down after the operation completes, even if an exception occurs.
+ /// A reference to a object containing details about the image file path and
+ /// destination drive. The and
+ /// properties must not be null, empty, or whitespace.
+ /// The source path of the driver or drivers to be added. This can be a single driver file path or an array of
+ /// driver paths. The value must not be null, empty, or whitespace.
+ /// Thrown if the directory specified in does not exist.
+ /// Thrown if the current process does not have administrative privileges required to initialize the DISM API.
internal static void AddDrivers(ref Parameters parameters, string DriversSource)
{
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.ImageFilePath, nameof(parameters.ImageFilePath));
@@ -56,10 +67,17 @@ internal static void AddDrivers(ref Parameters parameters, string DriversSource)
}
///
- /// Deploys an image of Windows to the specified .
- /// What gets installed is specified by and the .
+ /// Applies a Windows image to the specified destination drive.
///
- ///
+ /// This method uses the Deployment Image Servicing and Management (DISM) tool to apply
+ /// the specified image. Ensure that the destination drive does not already contain a Windows deployment, as the
+ /// method will not overwrite an existing installation. Administrative privileges are required to execute this
+ /// operation.
+ /// A object containing the necessary details for the operation, including the
+ /// destination drive, image file path, disk number, and image index.
+ /// An integer representing the exit code of the operation. Returns 1 if the destination drive already
+ /// contains a Windows deployment. Otherwise, returns the exit code of the underlying process.
+ /// Thrown if the current user does not have administrative privileges required to perform the operation.
internal static int ApplyImage(ref Parameters parameters)
{
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.DestinationDrive, nameof(parameters.DestinationDrive));
@@ -84,10 +102,15 @@ internal static int ApplyImage(ref Parameters parameters)
}
///
- /// Gets the image file from the source drive.
+ /// Determines the path to a valid image file (either "install.esd" or "install.wim") located in the "sources"
+ /// directory of the specified source drive.
///
- ///
- ///
+ /// This method checks for the presence of "install.esd" and "install.wim" files in the
+ /// "sources" directory of the drive specified by . If both files are
+ /// present, "install.esd" is returned.
+ /// A reference to a object containing the source drive and image file path. The property must specify the root directory of the source drive.
+ /// The full path to the image file ("install.esd" or "install.wim") if found.
internal static string GetImageFile(ref Parameters parameters)
{
try
@@ -121,9 +144,14 @@ internal static string GetImageFile(ref Parameters parameters)
}
///
- /// Gets all Windows editions available using DISM, if any.
+ /// Retrieves and displays information about the images contained in the specified image file.
///
- ///
+ /// This method initializes the DISM API, retrieves image information from the specified
+ /// file, and outputs details about each image to the console. The method requires administrative privileges
+ /// to execute and will throw an exception if the caller lacks sufficient privileges.
+ /// A reference to a object containing the path to the image file. The property must not be null or empty.
+ /// Thrown if the caller does not have administrative privileges.
internal static void GetImageInfo(ref Parameters parameters)
{
ArgumentException.ThrowIfNullOrEmpty(parameters.ImageFilePath, nameof(parameters));
@@ -175,10 +203,16 @@ internal static void GetImageInfo(ref Parameters parameters)
}
///
- /// Gets all Windows editions available using DISM, if any.
+ /// Retrieves information about the images contained in the specified image file.
///
- ///
- ///
+ /// This method initializes the DISM API to retrieve image information and ensures proper
+ /// shutdown of the API after the operation is complete. Ensure that the caller has sufficient privileges to
+ /// execute this method.
+ /// A reference to a object that contains the path to the image file. The property must not be null, empty, or consist only of whitespace.
+ /// A containing details about the images in the specified file.
+ /// Thrown if the is null, empty, or consists only of whitespace.
+ /// Thrown if the current user does not have administrative privileges required to initialize the DISM API.
internal static DismImageInfoCollection GetImageInfoT(ref Parameters parameters)
{
if (string.IsNullOrEmpty(parameters.ImageFilePath) ||
@@ -217,10 +251,18 @@ internal static DismImageInfoCollection GetImageInfoT(ref Parameters parameters)
}
///
- /// Installs the bootloader to the EFI drive of a new Windows installation.
+ /// Installs the bootloader to the specified EFI system partition.
///
- ///
+ /// This method requires administrative privileges to execute. Ensure that the calling
+ /// process has sufficient permissions. The method validates the existence of required directories and checks
+ /// for conflicts before proceeding with the installation.
+ /// A reference to a object containing the configuration for the bootloader
+ /// installation. The property specifies the drive containing the
+ /// Windows installation. The property specifies the EFI system partition
+ /// where the bootloader will be installed. The property specifies the
+ /// firmware type (e.g., "UEFI" or "BIOS").
+ /// The exit code of the bootloader installation process. A value of 0 typically indicates success.
+ /// Thrown if the current process does not have administrative privileges required to perform the installation.
internal static int InstallBootloader(ref Parameters parameters)
{
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.DestinationDrive, nameof(parameters.DestinationDrive));
diff --git a/WindowsInstallerLib/src/DiskManager.cs b/WindowsInstallerLib/src/DiskManager.cs
index 1ee4ac2..7f4780f 100644
--- a/WindowsInstallerLib/src/DiskManager.cs
+++ b/WindowsInstallerLib/src/DiskManager.cs
@@ -7,8 +7,11 @@
namespace WindowsInstallerLib
{
///
- /// Manages the disks on the system.
+ /// Provides functionality for managing and interacting with system disks on Windows platforms.
///
+ /// This class includes methods for formatting disks, listing available disks, and retrieving
+ /// disk information. It requires administrative privileges for certain operations, such as formatting a
+ /// disk.
[SupportedOSPlatform("windows")]
internal class DiskManager
{
@@ -41,8 +44,13 @@ internal static int FormatDisk(ref Parameters parameters)
}
///
- /// Lists all the disks on the system.
+ /// Lists all disk drives on the system and displays their details, including disk number, model, and device ID.
///
+ /// This method queries the system's disk drives using WMI (Windows Management
+ /// Instrumentation) and outputs the retrieved information to the console. The details include the disk number,
+ /// model, and device ID for each disk drive found. Note that this method is intended for internal use
+ /// and writes directly to the console. It does not return the retrieved data or provide a way to
+ /// programmatically access it.
internal static void ListAll()
{
try
@@ -64,9 +72,9 @@ internal static void ListAll()
}
///
- /// Lists all disk on the system using DriveInfo.
+ /// Retrieves an array of all available drive information on the system.
///
- ///
+ /// An array of objects representing the drives available on the system.
internal static DriveInfo[] GetDisksT()
{
try
diff --git a/WindowsInstallerLib/src/InstallerManager.cs b/WindowsInstallerLib/src/InstallerManager.cs
index b7221b2..18a9e6d 100644
--- a/WindowsInstallerLib/src/InstallerManager.cs
+++ b/WindowsInstallerLib/src/InstallerManager.cs
@@ -6,16 +6,19 @@
namespace WindowsInstallerLib
{
///
- /// Contains the parameters required for installing Windows.
+ /// Represents the parameters required for configuring and executing a disk imaging operation.
///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
+ /// This structure encapsulates all the necessary information for performing a disk imaging
+ /// process, including source and destination drives, image file details, and additional configuration
+ /// options.
+ /// Gets or sets the destination drive where the image will be applied.
+ /// Gets or sets the EFI (Extensible Firmware Interface) drive used during the imaging process.
+ /// Gets or sets the disk number associated with the operation.
+ /// Gets or sets the source drive containing the data to be imaged.
+ /// Gets or sets the index of the image within the image file to be applied.
+ /// Gets or sets the file path of the image file to be used in the operation.
+ /// Gets or sets a value indicating whether additional drivers should be installed during the imaging process.
+ /// Gets or sets the firmware type of the system being imaged.
[SupportedOSPlatform("windows")]
public struct Parameters(string DestinationDrive,
string EfiDrive,
@@ -37,17 +40,25 @@ public struct Parameters(string DestinationDrive,
}
///
- /// Manages the installation of Windows.
+ /// Provides functionality to configure and install Windows on a specified disk.
///
+ /// This class is designed for use on Windows platforms and provides methods to set up the
+ /// environment and deploy Windows installations. It requires valid parameters to be provided for successful
+ /// operation.
[SupportedOSPlatform("windows")]
public sealed class InstallerManager
{
///
- /// Sets up the environment correctly for deploying Windows.
+ /// Configures the specified object by prompting the user for missing values.
///
- ///
- ///
- ///
+ /// This method ensures that all required properties of the
+ /// object are populated. If any property is missing or invalid, the user is prompted to provide the necessary
+ /// input. The method validates user input and throws exceptions for invalid or incomplete data.
+ /// A reference to the object to configure. This object must not be null, and its
+ /// properties will be updated based on user input.
+ /// Thrown if the user provides invalid input, such as an improperly formatted drive letter or missing required
+ /// values.
+ /// Thrown if the firmware type cannot be determined or is invalid.
public static void Configure(ref Parameters parameters)
{
#region DestinationDrive
@@ -276,9 +287,18 @@ public static void Configure(ref Parameters parameters)
}
///
- /// Installs Windows on the specified disk.
+ /// Installs Windows on the specified disk and configures the bootloader.
///
- ///
+ /// This method performs the following steps: 1. Formats the specified disk. 2. Applies
+ /// the Windows image to the destination drive. 3. Installs the bootloader on the EFI drive. Each step relies
+ /// on the values provided in the object. Ensure all required properties are
+ /// correctly set before calling this method. If any step fails, the corresponding exception will propagate to
+ /// the caller.
+ /// A reference to a object containing the necessary configuration for the
+ /// installation. This includes the disk number, EFI drive, destination drive, image file path, image index, and
+ /// firmware type.
+ /// Thrown if the object contains invalid or missing values, such as: - Disk
+ /// number is -1. - EFI drive is null, empty, or whitespace.
[SupportedOSPlatform("windows")]
public static void InstallWindows(ref Parameters parameters)
{
diff --git a/WindowsInstallerLib/src/PrivilegesManager.cs b/WindowsInstallerLib/src/PrivilegesManager.cs
index 5d1e025..a206d8f 100644
--- a/WindowsInstallerLib/src/PrivilegesManager.cs
+++ b/WindowsInstallerLib/src/PrivilegesManager.cs
@@ -6,14 +6,21 @@
namespace WindowsInstallerLib
{
///
- /// Manages the privileges of the current user.
+ /// Provides utility methods for managing and checking user privileges.
///
+ /// This class includes methods to determine the current user's privilege level, such as whether
+ /// the user has administrative rights. It is designed for use on Windows platforms and may throw exceptions if
+ /// security or identity-related issues occur.
internal static class PrivilegesManager
{
///
- /// Checks if the current user is an administrator.
+ /// Determines whether the current user has administrative privileges on a Windows platform.
///
- ///
+ /// This method is only supported on Windows platforms. It uses the and classes to check if the current user belongs to
+ /// the Administrators group.
+ /// if the current user is a member of the Administrators group; otherwise, .
[SupportedOSPlatform("windows")]
internal static bool IsAdmin()
{
diff --git a/WindowsInstallerLib/src/ProcessManager.cs b/WindowsInstallerLib/src/ProcessManager.cs
index 9f93231..2d43b6a 100644
--- a/WindowsInstallerLib/src/ProcessManager.cs
+++ b/WindowsInstallerLib/src/ProcessManager.cs
@@ -5,12 +5,30 @@
namespace WindowsInstallerLib
{
///
- /// Handles the creation and management of processes.
+ /// Provides utility methods for managing and executing system processes.
///
+ /// The class includes methods for starting and managing various
+ /// types of processes, such as command-line tools, disk partitioning utilities, and deployment tools. It also
+ /// tracks the exit code of the last executed process. This class is intended for internal use and is not
+ /// thread-safe.
internal sealed class ProcessManager
{
+ ///
+ /// Gets the exit code that represents the result of the process's execution.
+ ///
internal static int ExitCode { get; private set; }
+ ///
+ /// Starts a command-line process with the specified file name and arguments, waits for it to complete, and
+ /// returns its exit code.
+ ///
+ /// The method uses a non-shell execution mode ( is set to ). The caller
+ /// is responsible for ensuring that the specified file exists and is executable.
+ /// The name or path of the executable file to start. This cannot be null or empty.
+ /// The command-line arguments to pass to the process. This can be null or an empty string if no arguments are
+ /// required.
+ /// The exit code of the process after it has completed execution.
internal static int StartCmdProcess(string fileName, string args)
{
try
@@ -40,6 +58,24 @@ internal static int StartCmdProcess(string fileName, string args)
return ExitCode;
}
+ ///
+ /// Executes a DiskPart process to format and partition a specified disk.
+ ///
+ /// This method uses the DiskPart utility to perform the following operations on the
+ /// specified disk: - Selects the specified disk.
+ /// - Cleans the disk.
- Converts the disk to GPT
+ /// format.
- Creates an MSR partition.
+ /// - Creates and formats an EFI partition with the FAT32 file system.
+ /// - Assigns the specified drive letter to the EFI partition.
+ /// - Creates and formats a primary partition with the NTFS file system.
+ /// - Assigns the specified drive letter to the primary partition.
+ /// The method writes commands to the DiskPart process via standard input and waits for the process to
+ /// complete.
+ /// The number of the disk to be formatted and partitioned.
+ /// The drive letter to assign to the EFI partition.
+ /// The drive letter to assign to the primary partition.
+ /// The exit code of the DiskPart process. A value of 0 indicates success, while a non-zero value indicates
+ /// failure.
internal static int StartDiskPartProcess(int DiskNumber, string EfiDrive, string DestinationDrive)
{
Process process = new();
@@ -108,6 +144,17 @@ internal static int StartDiskPartProcess(int DiskNumber, string EfiDrive, string
return ExitCode;
}
+ ///
+ /// Starts a new process to execute the Deployment Image Servicing and Management (DISM) tool with the specified
+ /// arguments.
+ ///
+ /// This method starts the DISM tool as a separate process and waits for it to complete
+ /// execution. The caller is responsible for ensuring that the provided arguments are valid and appropriate for
+ /// the DISM tool.
+ /// The command-line arguments to pass to the DISM tool. This must be a valid string of arguments supported by
+ /// DISM.
+ /// The exit code returned by the DISM process. A value of 0 typically indicates success, while non-zero values
+ /// indicate an error or failure.
internal static int StartDismProcess(string args)
{
Process process = new();
@@ -141,6 +188,17 @@ internal static int StartDismProcess(string args)
return ExitCode;
}
+ ///
+ /// Starts a process with the specified executable file and arguments, waits for it to exit, and returns the
+ /// process's exit code.
+ ///
+ /// The method uses a non-shell execution model ( is set to ). The caller
+ /// is responsible for ensuring that the specified executable file exists and is accessible.
+ /// The path to the executable file to start. This cannot be null or empty.
+ /// The command-line arguments to pass to the executable. This can be null or empty if no arguments are
+ /// required.
+ /// The exit code of the process after it has completed execution.
internal static int StartProcess(string filename, string args)
{
Process process = new();
diff --git a/WindowsInstallerLib/src/SystemInfoManager.cs b/WindowsInstallerLib/src/SystemInfoManager.cs
index 90e7401..18c2f4c 100644
--- a/WindowsInstallerLib/src/SystemInfoManager.cs
+++ b/WindowsInstallerLib/src/SystemInfoManager.cs
@@ -5,25 +5,35 @@
namespace WindowsInstallerLib
{
+ ///
+ /// Provides methods for retrieving system firmware information and determining the firmware type.
+ ///
+ /// This class includes functionality to check if the system is using EFI (Extensible Firmware
+ /// Interface) firmware. It is supported only on Windows platforms.
[SupportedOSPlatform("windows")]
internal static partial class SystemInfoManager
{
///
- /// Retrieves the firmware type of the system.
+ /// Retrieves the firmware type of the system by querying a firmware environment variable.
///
- ///
- ///
- ///
- ///
-
+ /// This method is a platform invocation (P/Invoke) wrapper for the native Windows API
+ /// function GetFirmwareEnvironmentVariableA. It allows managed code to interact with firmware
+ /// environment variables.
+ /// The name of the firmware environment variable to query.
+ /// The globally unique identifier (GUID) of the firmware environment variable namespace.
+ /// A pointer to a buffer that receives the value of the firmware environment variable.
+ /// The size, in bytes, of the buffer pointed to by .
+ /// The size of the data retrieved, in bytes, if the call succeeds; otherwise, 0 if the call fails.
[LibraryImport("kernel32.dll", EntryPoint = "GetFirmwareEnvironmentVariableA", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
private static partial uint GetFirmwareType(string lpName, string lpGUID, IntPtr pBuffer, uint size);
///
- /// Checks if the system is using EFI firmware.
+ /// Determines whether the system supports EFI (Extensible Firmware Interface).
///
- ///
+ /// This method checks the system's firmware type by invoking a low-level function and
+ /// analyzing the result. It relies on the last Win32 error code to determine EFI support.
+ /// if the system supports EFI; otherwise, .
internal static bool IsEFI()
{
// Call the function with a dummy variable name and a dummy variable namespace (function will fail because these don't exist.)
From 3e5f85fc73413a15325ebedc5ad0e48c3b301802 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 19 Jun 2025 18:05:10 +0100
Subject: [PATCH 04/30] Add installer compilation for the build system (#39)
* Add script for downloading the installer
* Add script to patch the installer
* Add scripts to compile the installer
* scripts: minor adjustments
- Call publish-cleanup.bat before building the installer, otherwise the script won't find the files.
- Set OUTPUTDIR to a more meaningful named folder.
* scripts: add build-installer folder removal and improve messages
---
scripts/cleanup.bat | 5 +--
scripts/compile-installer-cleanup.bat | 49 +++++++++++++++++++++++++++
scripts/compile-installer.bat | 17 ++++++++++
scripts/download-installer-script.ps1 | 15 ++++++++
scripts/patch-installer-script.ps1 | 28 +++++++++++++++
5 files changed, 112 insertions(+), 2 deletions(-)
create mode 100644 scripts/compile-installer-cleanup.bat
create mode 100644 scripts/compile-installer.bat
create mode 100644 scripts/download-installer-script.ps1
create mode 100644 scripts/patch-installer-script.ps1
diff --git a/scripts/cleanup.bat b/scripts/cleanup.bat
index c28821d..22180da 100644
--- a/scripts/cleanup.bat
+++ b/scripts/cleanup.bat
@@ -2,6 +2,7 @@
CALL :REMOVEDIR %~dp0..\.vs
CALL :REMOVEDIR %~dp0..\build
+CALL :REMOVEDIR %~dp0..\build-installer
CALL :REMOVEDIR %~dp0..\ConsoleApp\bin
CALL :REMOVEDIR %~dp0..\ConsoleApp\obj
CALL :REMOVEDIR %~dp0..\WindowsInstallerLib\bin
@@ -17,9 +18,9 @@ IF %ERRORLEVEL%==0 (
:REMOVEDIR
IF EXIST %* (
- ECHO REMOVING %* DIRECTORY...
+ ECHO REMOVING DIRECTORY: %*...
RMDIR /S /Q %*
CALL :EXITWITHERROR
) ELSE (
- ECHO %* DIRECTORY NOT FOUND
+ ECHO [!] DIRECTORY NOT FOUND: %*
)
diff --git a/scripts/compile-installer-cleanup.bat b/scripts/compile-installer-cleanup.bat
new file mode 100644
index 0000000..4b6e2f3
--- /dev/null
+++ b/scripts/compile-installer-cleanup.bat
@@ -0,0 +1,49 @@
+@ECHO off
+
+CALL %~dp0publish-cleanup.bat
+
+SET COMPILER="%LOCALAPPDATA%\Programs\Inno Setup 6\ISCC.exe" /Q
+FOR /F "tokens=2 delims=\\" %%A IN ('whoami') DO SET USERNAME="%%A"
+SET LICENSE="%~dp0..\LICENSE"
+SET OUTPUTDIR="%~dp0..\build-installer"
+SET SETUPSCRIPT="%~dp0wcit-setup.iss"
+SET USERNAME="felgmar"
+
+IF NOT EXIST %LICENSE% (
+ ECHO.ERROR: LICENSE FILE NOT FOUND
+ EXIT /B 1
+)
+
+IF EXIST %SETUPSCRIPT% (
+ DEL /Q %SETUPSCRIPT%
+)
+
+IF NOT EXIST %SETUPSCRIPT% (
+ CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0download-installer-script.ps1 -OutputPath %SETUPSCRIPT%}"
+ CALL icacls.exe "%SETUPSCRIPT%" /grant %USERNAME%:F
+)
+
+IF NOT EXIST %OUTPUTDIR% (
+ MKDIR %OUTPUTDIR%
+)
+
+FOR /F "tokens=* delims=.exe" %%F IN ('DIR /A /B "%OUTPUTDIR%"') DO (
+ ECHO REMOVING FILE %%F FROM %OUTPUTDIR%...
+ DEL /Q "%OUTPUTDIR%\%%F"
+)
+
+IF "%1"=="" IF NOT EXIST %SETUPSCRIPT% (
+ ECHO.ERROR: NO INNO SETUP SCRIPT WAS PROVIDED
+)
+
+ECHO PATCHING INNO SETUP SCRIPT...
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppOutputDir -Value %OUTPUTDIR%}"
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define UserName -Value %USERNAME%}"
+
+ECHO COMPILING INSTALLER...
+CALL %COMPILER% %SETUPSCRIPT%
+
+IF %ERRORLEVEL% NEQ 0 (
+ ECHO.COMPILATION FAILED WITH CODE %ERRORLEVEL%
+ EXIT /B %ERRORLEVEL%
+)
diff --git a/scripts/compile-installer.bat b/scripts/compile-installer.bat
new file mode 100644
index 0000000..12c098d
--- /dev/null
+++ b/scripts/compile-installer.bat
@@ -0,0 +1,17 @@
+@ECHO off
+
+CALL %~dp0cleanup.bat
+
+dotnet publish "Windows Installer.sln" --nologo --self-contained --property:OutputPath=%~dp0..\build\ --configuration "Release"
+
+MOVE /Y %~dp0..\build\publish %~dp0..\
+RMDIR /Q /S %~dp0..\build
+MOVE /Y %~dp0..\publish %~dp0..\build
+
+SET COMPILER="%LOCALAPPDATA%\Programs\Inno Setup 6\ISCC.exe"
+
+IF %1.==. (
+ echo ERROR: NO INNO SETUP SCRIPT WAS PROVIDED
+) ELSE (
+ CALL %COMPILER% %1
+)
diff --git a/scripts/download-installer-script.ps1 b/scripts/download-installer-script.ps1
new file mode 100644
index 0000000..f50d8e6
--- /dev/null
+++ b/scripts/download-installer-script.ps1
@@ -0,0 +1,15 @@
+[CMDLetBinding()]
+param(
+ [Parameter(Mandatory=$false)]
+ [String]$OutputPath
+)
+
+process {
+ [String]$Uri="https://raw.githubusercontent.com/felgmar/isscripts/refs/heads/main/wcit/wcit-setup.iss"
+ try {
+ Invoke-WebRequest -Uri $Uri -OutFile "$OutputPath" -UseBasicParsing -WarningAction Ignore -ErrorAction Stop
+ }
+ catch {
+ throw $_.Exception.Message
+ }
+}
diff --git a/scripts/patch-installer-script.ps1 b/scripts/patch-installer-script.ps1
new file mode 100644
index 0000000..67524c4
--- /dev/null
+++ b/scripts/patch-installer-script.ps1
@@ -0,0 +1,28 @@
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory = $true)]
+ [string]$OutputPath,
+ [Parameter(Mandatory = $true)]
+ [ValidateSet('AppOutputDir', 'UserName', 'AppLicense')]
+ [String]$Define,
+ [Parameter(Mandatory = $true)]
+ [String]$Value
+)
+
+process {
+ try {
+ foreach ($Field in $Define) {
+ $ScriptFile = Get-Content -Path $OutputPath -Raw
+ [String]$Pattern = "(#define\s$Field\s+).*"
+ [String]$Replacement = "`$1`"$Value`""
+ $Patch = $ScriptFile -replace $Pattern, $Replacement
+ Set-Content -Path $OutputPath -Value $Patch -Force
+ }
+ }
+ catch {
+ throw $_.Exception.Message
+ }
+ finally {
+ Write-Host "Updated $Define in $OutputPath to '$Value'"
+ }
+}
From bda97770443124a30973a4a92d7b854ff368da0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 19 Jun 2025 19:49:03 +0100
Subject: [PATCH 05/30] Potential fix for code scanning alert no. 1: Workflow
does not contain permissions (#43)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Felipe González Martín <158048821+felgmar@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
---
.github/workflows/dotnet.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 5e731e4..1ef9297 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -2,6 +2,8 @@
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
name: .NET
+permissions:
+ contents: read
on:
push:
From fdfe2dde1acadc92ecc0254c23b74b47c6d2a186 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 19 Jun 2025 21:53:08 +0100
Subject: [PATCH 06/30] Update gitignore with the latest Visual Studio template
---
.gitignore | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 419 insertions(+), 6 deletions(-)
diff --git a/.gitignore b/.gitignore
index fab1644..89dd7af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,420 @@
-bin/
-inno_setup/
-obj/
-Properties/
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
*.user
-.*
-build
+*.userosscache
+*.sln.docstates
+build/
+build-installer/
+scripts/*.iss
+
+# 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/
+[Aa][Rr][Mm]64[Ee][Cc]/
+bld/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Build results on 'Bin' directories
+**/[Bb]in/*
+# Uncomment if you have tasks that rely on *.refresh files to move binaries
+# (https://github.com/github/gitignore/pull/3736)
+#!**/[Bb]in/*.refresh
+
+# 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.*
+*.trx
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Approval Tests result files
+*.received.*
+
+# 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
+*.idb
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+# but not Directory.Build.rsp, as it configures directory-level build defaults
+!Directory.Build.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_*
+.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
+
+# 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 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# 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
+MSBuild_Logs/
+
+# AWS SAM Build and Temporary Artifacts folder
+.aws-sam
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+**/.mfractor/
+
+# Local History for Visual Studio
+**/.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# 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
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
From 31b5b62d59229ceae0cfea16f9a89dcb48a06a81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Sun, 13 Jul 2025 10:57:14 +0100
Subject: [PATCH 07/30] WindowsInstallerLib: don't create the MSR partition
first
Creating the MSR partition before the EFI partition is not the default behaviour of Microsoft's installer.
---
WindowsInstallerLib/src/ProcessManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/WindowsInstallerLib/src/ProcessManager.cs b/WindowsInstallerLib/src/ProcessManager.cs
index 2d43b6a..f4859ce 100644
--- a/WindowsInstallerLib/src/ProcessManager.cs
+++ b/WindowsInstallerLib/src/ProcessManager.cs
@@ -93,8 +93,8 @@ internal static int StartDiskPartProcess(int DiskNumber, string EfiDrive, string
process.StandardInput.WriteLine($"select disk {DiskNumber}");
process.StandardInput.WriteLine("clean");
process.StandardInput.WriteLine("convert gpt");
- process.StandardInput.WriteLine("create partition msr size=16");
process.StandardInput.WriteLine("create partition efi size=100");
+ process.StandardInput.WriteLine("create partition msr size=16");
process.StandardInput.WriteLine("format fs=fat32 quick");
process.StandardInput.WriteLine($"assign letter {EfiDrive}");
process.StandardInput.WriteLine("create partition primary");
From 6d4cfba59ba6596a64f9cdb36ffe65ca7118514f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Sun, 13 Jul 2025 10:58:25 +0100
Subject: [PATCH 08/30] DeploymentManager: small refactoring for improved
readability
---
WindowsInstallerLib/src/DeployManager.cs | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/WindowsInstallerLib/src/DeployManager.cs b/WindowsInstallerLib/src/DeployManager.cs
index b4d86b1..edc8ea2 100644
--- a/WindowsInstallerLib/src/DeployManager.cs
+++ b/WindowsInstallerLib/src/DeployManager.cs
@@ -1,8 +1,7 @@
+using Microsoft.Dism;
using System;
using System.IO;
using System.Runtime.Versioning;
-using System.Threading;
-using Microsoft.Dism;
namespace WindowsInstallerLib
{
@@ -30,20 +29,23 @@ internal static class DeployManager
/// driver paths. The value must not be null, empty, or whitespace.
/// Thrown if the directory specified in does not exist.
/// Thrown if the current process does not have administrative privileges required to initialize the DISM API.
- internal static void AddDrivers(ref Parameters parameters, string DriversSource)
+ internal static void AddDrivers(ref Parameters parameters,
+ string DriversSource,
+ bool ForceUnsigned = false,
+ bool Recursive = false)
{
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.ImageFilePath, nameof(parameters.ImageFilePath));
ArgumentException.ThrowIfNullOrWhiteSpace(DriversSource, nameof(DriversSource));
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.DestinationDrive, nameof(parameters.DestinationDrive));
- if (!Directory.Exists(parameters.DestinationDrive))
+ if (!PrivilegesManager.IsAdmin())
{
- throw new DirectoryNotFoundException($"Could not find the directory: {parameters.DestinationDrive}");
+ throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
}
- if (!PrivilegesManager.IsAdmin())
+ if (!Directory.Exists(parameters.DestinationDrive))
{
- throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
+ throw new DirectoryNotFoundException($"Could not find the directory: {parameters.DestinationDrive}");
}
try
@@ -53,11 +55,11 @@ internal static void AddDrivers(ref Parameters parameters, string DriversSource)
if (DriversSource.GetType().IsArray)
{
- DismApi.AddDriversEx(session, DriversSource, forceUnsigned: false, recursive: true);
+ DismApi.AddDriversEx(session, DriversSource, ForceUnsigned, Recursive);
}
else
{
- DismApi.AddDriver(session, DriversSource, forceUnsigned: false);
+ DismApi.AddDriver(session, DriversSource, ForceUnsigned);
}
}
finally
From b70fc51b3bce3bc05315891150b9485386038550 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Wed, 6 Aug 2025 16:13:34 +0100
Subject: [PATCH 09/30] WindowsInstallerLib: add new function
InstallAdditionalDrivers
- Also update old naming
---
WindowsInstallerLib/src/DeployManager.cs | 54 +++++++++++++++++++-
WindowsInstallerLib/src/InstallerManager.cs | 56 +++++++++++++++++++--
2 files changed, 105 insertions(+), 5 deletions(-)
diff --git a/WindowsInstallerLib/src/DeployManager.cs b/WindowsInstallerLib/src/DeployManager.cs
index edc8ea2..f1d6f68 100644
--- a/WindowsInstallerLib/src/DeployManager.cs
+++ b/WindowsInstallerLib/src/DeployManager.cs
@@ -252,6 +252,56 @@ internal static DismImageInfoCollection GetImageInfoT(ref Parameters parameters)
}
}
+ ///
+ /// Installs additional drivers to the specified offline Windows image.
+ ///
+ ///
+ ///
+ internal static void InstallAdditionalDrivers(ref Parameters parameters)
+ {
+ DismSession session;
+
+ ArgumentException.ThrowIfNullOrWhiteSpace(parameters.AdditionalDriversDrive, nameof(parameters));
+
+ if (!PrivilegesManager.IsAdmin())
+ {
+ throw new UnauthorizedAccessException("You do not have enough privileges to install additional drivers.");
+ }
+
+ try
+ {
+ switch (PrivilegesManager.IsAdmin())
+ {
+ case true:
+ DismApi.Initialize(DismLogLevel.LogErrorsWarnings);
+ break;
+ case false:
+ throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
+ }
+
+ session = DismApi.OpenOfflineSession(parameters.DestinationDrive);
+
+ }
+ catch (DismException)
+ {
+ throw;
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+
+ try
+ {
+ DismApi.AddDriversEx(session, parameters.AdditionalDriversDrive, false, true);
+ }
+ finally
+ {
+ session.Close();
+ DismApi.Shutdown();
+ }
+ }
+
///
/// Installs the bootloader to the specified EFI system partition.
///
@@ -290,7 +340,7 @@ internal static int InstallBootloader(ref Parameters parameters)
{
throw new IOException($"The drive letter {parameters.EfiDrive} is already in use.");
}
-
+
if (!WINDIR_EXISTS)
{
throw new DirectoryNotFoundException(@$"The directory {WINDIR_PATH} does not exist!");
@@ -308,7 +358,7 @@ internal static int InstallBootloader(ref Parameters parameters)
{
throw;
}
-
+
return ProcessManager.ExitCode;
}
}
diff --git a/WindowsInstallerLib/src/InstallerManager.cs b/WindowsInstallerLib/src/InstallerManager.cs
index 18a9e6d..1657743 100644
--- a/WindowsInstallerLib/src/InstallerManager.cs
+++ b/WindowsInstallerLib/src/InstallerManager.cs
@@ -17,7 +17,7 @@ namespace WindowsInstallerLib
/// Gets or sets the source drive containing the data to be imaged.
/// Gets or sets the index of the image within the image file to be applied.
/// Gets or sets the file path of the image file to be used in the operation.
- /// Gets or sets a value indicating whether additional drivers should be installed during the imaging process.
+ /// Gets or sets a value indicating whether additional drivers should be installed during the imaging process.
/// Gets or sets the firmware type of the system being imaged.
[SupportedOSPlatform("windows")]
public struct Parameters(string DestinationDrive,
@@ -26,7 +26,7 @@ public struct Parameters(string DestinationDrive,
string SourceDrive,
int ImageIndex,
string ImageFilePath,
- bool InstallExtraDrivers,
+ string AdditionalDriversDrive,
string FirmwareType)
{
public string DestinationDrive { get; set; } = DestinationDrive;
@@ -35,7 +35,7 @@ public struct Parameters(string DestinationDrive,
public string SourceDrive { get; set; } = SourceDrive;
public int ImageIndex { get; set; } = ImageIndex;
public string ImageFilePath { get; set; } = ImageFilePath;
- public bool InstallExtraDrivers { get; set; } = InstallExtraDrivers;
+ public string AdditionalDriversDrive { get; set; } = AdditionalDriversDrive;
public string FirmwareType { get; set; } = FirmwareType;
}
@@ -284,6 +284,47 @@ public static void Configure(ref Parameters parameters)
}
}
#endregion
+
+ #region AdditionalDriversList
+ if (string.IsNullOrWhiteSpace(parameters.AdditionalDriversDrive))
+ {
+ Console.Write("\n=> Do you want to add additional drivers to your installation?: [Y/N]: ");
+ string? UserWantsExtraDrivers = Console.ReadLine()?.ToLower(CultureInfo.CurrentCulture);
+
+ if (string.IsNullOrEmpty(UserWantsExtraDrivers) ||
+ string.IsNullOrWhiteSpace(UserWantsExtraDrivers))
+ {
+ UserWantsExtraDrivers = "no";
+ }
+
+ switch (UserWantsExtraDrivers.ToLower())
+ {
+ case "no":
+ return;
+ case "n":
+ break;
+ case "yes":
+ case "y":
+ Console.Write("\n==> Specify the directory where the drivers are located. (e.g. X:\\Drivers): ");
+ string? driversPath = Console.ReadLine();
+ if (string.IsNullOrEmpty(driversPath) ||
+ string.IsNullOrWhiteSpace(driversPath))
+ {
+ throw new ArgumentException("No drivers path was specified.");
+ }
+
+ if (!Directory.Exists(driversPath))
+ {
+ throw new FileNotFoundException($"The directory {driversPath} does not exist");
+ }
+
+ parameters.AdditionalDriversDrive = driversPath;
+ break;
+ default:
+ return;
+ }
+ }
+ #endregion
}
///
@@ -335,6 +376,15 @@ public static void InstallWindows(ref Parameters parameters)
throw;
}
+ try
+ {
+ DeployManager.InstallAdditionalDrivers(ref parameters);
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+
try
{
DeployManager.InstallBootloader(ref parameters);
From c42b0dedce592b2d5fbdde5cf7a8f369b8d23518 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Wed, 6 Aug 2025 16:14:20 +0100
Subject: [PATCH 10/30] ConsoleApp: update naming to AdditionalDriversDrive
---
ConsoleApp/src/ArgumentParser.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/ConsoleApp/src/ArgumentParser.cs b/ConsoleApp/src/ArgumentParser.cs
index 68f5c51..c6bafca 100644
--- a/ConsoleApp/src/ArgumentParser.cs
+++ b/ConsoleApp/src/ArgumentParser.cs
@@ -58,8 +58,8 @@ internal static void ParseArgs(ref Parameters parameters, string[] args)
case "/imagefilepath":
parameters.ImageFilePath = args[Array.IndexOf(args, arg) + 1].ToLowerInvariant();
continue;
- case "/installextradrivers":
- parameters.InstallExtraDrivers = true;
+ case "/additionaldriversdrive":
+ parameters.AdditionalDriversDrive = args[Array.IndexOf(args, arg) + 1].ToLowerInvariant();
continue;
case "/firmwaretype":
parameters.FirmwareType = args[Array.IndexOf(args, arg) + 1].ToUpperInvariant();
@@ -74,7 +74,7 @@ internal static void ParseArgs(ref Parameters parameters, string[] args)
Console.WriteLine($" Source Drive: {parameters.SourceDrive}");
Console.WriteLine($" Image Index: {parameters.ImageIndex}");
Console.WriteLine($" Image File Path: {parameters.ImageFilePath}");
- Console.WriteLine($" Install Extra Drivers: {parameters.InstallExtraDrivers}");
+ Console.WriteLine($" Additional Drivers Drive: {parameters.AdditionalDriversDrive}");
Console.WriteLine($" Firmware Type: {parameters.FirmwareType}");
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
From dd7d8e8889e2d05a9390fe9baf3bfdfee9de7731 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 15 Sep 2025 19:37:43 +0100
Subject: [PATCH 11/30] scripts: fix empty argument check
---
scripts/compile-installer.bat | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/compile-installer.bat b/scripts/compile-installer.bat
index 12c098d..9384d3f 100644
--- a/scripts/compile-installer.bat
+++ b/scripts/compile-installer.bat
@@ -10,7 +10,7 @@ MOVE /Y %~dp0..\publish %~dp0..\build
SET COMPILER="%LOCALAPPDATA%\Programs\Inno Setup 6\ISCC.exe"
-IF %1.==. (
+IF %1.=="" (
echo ERROR: NO INNO SETUP SCRIPT WAS PROVIDED
) ELSE (
CALL %COMPILER% %1
From c0f044a70c8bf5cba9090c286233012175201fb0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 15 Sep 2025 19:38:03 +0100
Subject: [PATCH 12/30] scripts: adjust indentation
---
scripts/download-installer-script.ps1 | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/download-installer-script.ps1 b/scripts/download-installer-script.ps1
index f50d8e6..f96b634 100644
--- a/scripts/download-installer-script.ps1
+++ b/scripts/download-installer-script.ps1
@@ -5,7 +5,8 @@ param(
)
process {
- [String]$Uri="https://raw.githubusercontent.com/felgmar/isscripts/refs/heads/main/wcit/wcit-setup.iss"
+ [String]$Uri = "https://raw.githubusercontent.com/felgmar/isscripts/refs/heads/main/wcit/wcit-setup.iss"
+
try {
Invoke-WebRequest -Uri $Uri -OutFile "$OutputPath" -UseBasicParsing -WarningAction Ignore -ErrorAction Stop
}
From e621dc873ec6317e3f3ce19686e4d433ec857ad6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 15 Sep 2025 19:38:25 +0100
Subject: [PATCH 13/30] scripts: fix and improve some stuff
---
scripts/compile-installer-cleanup.bat | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/scripts/compile-installer-cleanup.bat b/scripts/compile-installer-cleanup.bat
index 4b6e2f3..ebe5f12 100644
--- a/scripts/compile-installer-cleanup.bat
+++ b/scripts/compile-installer-cleanup.bat
@@ -6,7 +6,7 @@ SET COMPILER="%LOCALAPPDATA%\Programs\Inno Setup 6\ISCC.exe" /Q
FOR /F "tokens=2 delims=\\" %%A IN ('whoami') DO SET USERNAME="%%A"
SET LICENSE="%~dp0..\LICENSE"
SET OUTPUTDIR="%~dp0..\build-installer"
-SET SETUPSCRIPT="%~dp0wcit-setup.iss"
+SET SETUPSCRIPT="%~dp0..\build-installer\wcit-setup.iss"
SET USERNAME="felgmar"
IF NOT EXIST %LICENSE% (
@@ -14,6 +14,10 @@ IF NOT EXIST %LICENSE% (
EXIT /B 1
)
+IF NOT EXIST %OUTPUTDIR% (
+ MKDIR %OUTPUTDIR%
+)
+
IF EXIST %SETUPSCRIPT% (
DEL /Q %SETUPSCRIPT%
)
@@ -23,17 +27,14 @@ IF NOT EXIST %SETUPSCRIPT% (
CALL icacls.exe "%SETUPSCRIPT%" /grant %USERNAME%:F
)
-IF NOT EXIST %OUTPUTDIR% (
- MKDIR %OUTPUTDIR%
-)
-
-FOR /F "tokens=* delims=.exe" %%F IN ('DIR /A /B "%OUTPUTDIR%"') DO (
+FOR %%F IN ("%OUTPUTDIR%\*.exe") DO (
ECHO REMOVING FILE %%F FROM %OUTPUTDIR%...
DEL /Q "%OUTPUTDIR%\%%F"
)
IF "%1"=="" IF NOT EXIST %SETUPSCRIPT% (
ECHO.ERROR: NO INNO SETUP SCRIPT WAS PROVIDED
+ EXIT /B 1
)
ECHO PATCHING INNO SETUP SCRIPT...
From d946c31405a5ade830d03dad8994b140ef42862d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 15 Sep 2025 19:49:17 +0100
Subject: [PATCH 14/30] scripts: implement RepositoryDir variable
---
scripts/compile-installer-cleanup.bat | 2 ++
scripts/patch-installer-script.ps1 | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/scripts/compile-installer-cleanup.bat b/scripts/compile-installer-cleanup.bat
index ebe5f12..5b97d49 100644
--- a/scripts/compile-installer-cleanup.bat
+++ b/scripts/compile-installer-cleanup.bat
@@ -5,6 +5,7 @@ CALL %~dp0publish-cleanup.bat
SET COMPILER="%LOCALAPPDATA%\Programs\Inno Setup 6\ISCC.exe" /Q
FOR /F "tokens=2 delims=\\" %%A IN ('whoami') DO SET USERNAME="%%A"
SET LICENSE="%~dp0..\LICENSE"
+SET REPOSITORYDIR="%~dp0.."
SET OUTPUTDIR="%~dp0..\build-installer"
SET SETUPSCRIPT="%~dp0..\build-installer\wcit-setup.iss"
SET USERNAME="felgmar"
@@ -38,6 +39,7 @@ IF "%1"=="" IF NOT EXIST %SETUPSCRIPT% (
)
ECHO PATCHING INNO SETUP SCRIPT...
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define RepositoryDir -Value %REPOSITORYDIR%}"
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppOutputDir -Value %OUTPUTDIR%}"
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define UserName -Value %USERNAME%}"
diff --git a/scripts/patch-installer-script.ps1 b/scripts/patch-installer-script.ps1
index 67524c4..95a0042 100644
--- a/scripts/patch-installer-script.ps1
+++ b/scripts/patch-installer-script.ps1
@@ -3,7 +3,7 @@ param (
[Parameter(Mandatory = $true)]
[string]$OutputPath,
[Parameter(Mandatory = $true)]
- [ValidateSet('AppOutputDir', 'UserName', 'AppLicense')]
+ [ValidateSet('AppOutputDir', 'UserName', 'AppLicense', 'RepositoryDir')]
[String]$Define,
[Parameter(Mandatory = $true)]
[String]$Value
From 79d4ec30aeb1f708c705a9f379558c44713fd898 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 15 Sep 2025 19:50:16 +0100
Subject: [PATCH 15/30] scripts: change AppLicense to %LICENSE%
---
scripts/compile-installer-cleanup.bat | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/compile-installer-cleanup.bat b/scripts/compile-installer-cleanup.bat
index 5b97d49..4b6aeee 100644
--- a/scripts/compile-installer-cleanup.bat
+++ b/scripts/compile-installer-cleanup.bat
@@ -42,6 +42,7 @@ ECHO PATCHING INNO SETUP SCRIPT...
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define RepositoryDir -Value %REPOSITORYDIR%}"
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppOutputDir -Value %OUTPUTDIR%}"
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define UserName -Value %USERNAME%}"
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppLicense -Value %LICENSE%}"
ECHO COMPILING INSTALLER...
CALL %COMPILER% %SETUPSCRIPT%
From f8c3128de612f1cdf436a8c0dc6c6c5090afd56b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 18 Sep 2025 11:53:33 +0100
Subject: [PATCH 16/30] Vastly improve the README file adding more information
---
README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 56 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index a8db7a2..6183090 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,58 @@
-# wcit
+# wcit – Windows CLI Installer Tool
-## What is this?
-Windows CLI Installer Tool is a program which deploys Windows onto any storage device within an existing Windows installation eliminating the need to reboot to install the OS. By running multiple instances of this tool it is possible to install Windows several times at once targeting as many devices as instances there are.
+## 📌 What is this?
+**Windows CLI Installer Tool** is a C# program that deploys Windows onto any storage device **from within an existing Windows installation**, eliminating the need to reboot into a separate installer.
-## Getting started
-Download the latest release from `Releases` at the right side.
+By running multiple instances, you can install Windows on several devices at once — as many as you have instances running. This can be useful for:
+- Rapid OS deployment in IT environments
+- Preparing multiple drives for testing or distribution
+- Automating repetitive installation tasks
+
+---
+
+## 🚀 Getting Started
+
+### Download Prebuilt Release
+If you just want to use the tool, grab the latest release from the **[Releases](../../releases)** section on the right-hand side of this page.
+
+---
+
+## 🛠 Building from Source
+
+### Prerequisites
+- **Windows 10/11**
+- **.NET SDK** (version X.X or later — replace with your actual target)
+- **Visual Studio** (Community Edition or higher) with `.NET desktop development` workload
+- *(Optional)* [Inno Setup](https://jrsoftware.org/isinfo.php) if you want to compile the installer
+
+---
+
+### Build Scripts Overview
+
+All build scripts are in the root folder. Run them from **Command Prompt** or **PowerShell**.
+
+| Script | What it does |
+|--------|--------------|
+| `build.bat` | Builds the project in Release mode |
+| `build-clean.bat` | Cleans previous build artifacts, then builds |
+| `publish.bat` | Publishes the project (self-contained build) |
+| `publish-cleanup.bat` | Cleans, then publishes |
+| `compile.bat` | Compiles without publishing |
+| `compile-installer.bat` | Builds and packages an installer using Inno Setup |
+| `cleanup.bat` | Removes build artifacts |
+| `patch-installer-scripts.ps1` | Updates installer scripts before compiling |
+| `installer-scripts.ps1` | Helper script for installer creation |
+
+---
+
+### Example Build Commands
+> **Note**: These commands must be ran at root directory of the repository.
+
+- To build and cleanup:
+```powershell
+.\scripts\build-clean.bat
+```
+- To compile the installer:
+```powershell
+.\scripts\publish-cleanup.bat
+```
From ee52cac1906a00e433691141ea1ef6703188a9b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 18 Sep 2025 13:43:25 +0100
Subject: [PATCH 17/30] Fix a typo
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6183090..22181a9 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@ All build scripts are in the root folder. Run them from **Command Prompt** or **
---
### Example Build Commands
-> **Note**: These commands must be ran at root directory of the repository.
+> **Note**: These commands must be ran at the root directory of the repository.
- To build and cleanup:
```powershell
From f0cda0e477c0801da357b80f420842238cf5228c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 18 Sep 2025 13:51:19 +0100
Subject: [PATCH 18/30] WindowsInstallerLib: reimplement extra driver
installation
- Added a separate function named AddDrivers for installing many drivers at once and AddDriver for a one .inf file at a time.
---
WindowsInstallerLib/src/DeployManager.cs | 106 +++++++++++++++++++----
1 file changed, 91 insertions(+), 15 deletions(-)
diff --git a/WindowsInstallerLib/src/DeployManager.cs b/WindowsInstallerLib/src/DeployManager.cs
index f1d6f68..903c220 100644
--- a/WindowsInstallerLib/src/DeployManager.cs
+++ b/WindowsInstallerLib/src/DeployManager.cs
@@ -16,10 +16,76 @@ namespace WindowsInstallerLib
internal static class DeployManager
{
///
- /// Adds one or more drivers to the specified offline Windows image.
+ /// Installs a driver to the specified offline Windows image.
///
/// This method initializes the DISM API, opens an offline session for the specified
- /// destination drive, and adds the provided drivers. If is an array, all
+ /// destination drive, and adds the provided drivers. If is an array, all
+ /// drivers in the array are added recursively. Otherwise, a single driver is added. The DISM API is properly
+ /// shut down after the operation completes, even if an exception occurs.
+ /// A reference to a object containing details about the image file path and
+ /// destination drive. The and
+ /// properties must not be null, empty, or whitespace.
+ /// The source path of the driver or drivers to be added. This can be a single driver file path or an array of
+ /// driver paths. The value must not be null, empty, or whitespace.
+ /// Thrown if the directory specified in does not exist.
+ /// Thrown if the current process does not have administrative privileges required to initialize the DISM API.
+ internal static void AddDriver(ref Parameters parameters,
+ string DriverSource,
+ bool ForceUnsigned = false)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(parameters.ImageFilePath, nameof(parameters.ImageFilePath));
+ ArgumentException.ThrowIfNullOrWhiteSpace(DriverSource, nameof(DriverSource));
+ ArgumentException.ThrowIfNullOrWhiteSpace(parameters.DestinationDrive, nameof(parameters.DestinationDrive));
+
+ if (!PrivilegesManager.IsAdmin())
+ {
+ throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
+ }
+
+ if (!Directory.Exists(parameters.DestinationDrive))
+ {
+ throw new DirectoryNotFoundException($"Could not find the directory: {parameters.DestinationDrive}");
+ }
+
+ DismSession? session = null;
+
+ try
+ {
+ DismApi.InitializeEx(DismLogLevel.LogErrorsWarnings);
+ }
+ catch (DismException)
+ {
+ throw;
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+
+ try
+ {
+ session = DismApi.OpenOfflineSession(parameters.DestinationDrive);
+ DismApi.AddDriver(session, DriverSource, ForceUnsigned);
+ }
+ catch (DismRebootRequiredException)
+ {
+ throw;
+ }
+ catch (DismException)
+ {
+ throw;
+ }
+ finally
+ {
+ DismApi.Shutdown();
+ }
+ }
+
+ ///
+ /// Installs multiple drivers at once to the specified offline Windows image.
+ ///
+ /// This method initializes the DISM API, opens an offline session for the specified
+ /// destination drive, and adds the provided drivers. If is an array, all
/// drivers in the array are added recursively. Otherwise, a single driver is added. The DISM API is properly
/// shut down after the operation completes, even if an exception occurs.
/// A reference to a object containing details about the image file path and
@@ -30,9 +96,9 @@ internal static class DeployManager
/// Thrown if the directory specified in does not exist.
/// Thrown if the current process does not have administrative privileges required to initialize the DISM API.
internal static void AddDrivers(ref Parameters parameters,
- string DriversSource,
- bool ForceUnsigned = false,
- bool Recursive = false)
+ string DriversSource,
+ bool ForceUnsigned = false,
+ bool Recursive = false)
{
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.ImageFilePath, nameof(parameters.ImageFilePath));
ArgumentException.ThrowIfNullOrWhiteSpace(DriversSource, nameof(DriversSource));
@@ -48,19 +114,29 @@ internal static void AddDrivers(ref Parameters parameters,
throw new DirectoryNotFoundException($"Could not find the directory: {parameters.DestinationDrive}");
}
+ DismSession? session = null;
+
try
{
- DismApi.Initialize(DismLogLevel.LogErrorsWarningsInfo);
- DismSession session = DismApi.OpenOfflineSession(parameters.DestinationDrive);
+ DismApi.InitializeEx(DismLogLevel.LogErrorsWarnings);
+ }
+ catch (DismException)
+ {
+ throw;
+ }
+ catch (Exception)
+ {
+ throw;
+ }
- if (DriversSource.GetType().IsArray)
- {
- DismApi.AddDriversEx(session, DriversSource, ForceUnsigned, Recursive);
- }
- else
- {
- DismApi.AddDriver(session, DriversSource, ForceUnsigned);
- }
+ try
+ {
+ session = DismApi.OpenOfflineSession(parameters.DestinationDrive);
+ DismApi.AddDriversEx(session, DriversSource, ForceUnsigned, Recursive);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ throw;
}
finally
{
From f105a502f69ab44d3c691547dda3b0bd3a98a052 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 18 Sep 2025 13:59:02 +0100
Subject: [PATCH 19/30] README: Update .NET SDK prerequisites
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Version 8.0 or later is required for this program to run nicely and to have official support from Microsoft, in the case of errors.
Signed-off-by: Felipe González Martín <158048821+felgmar@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 22181a9..d05e963 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ If you just want to use the tool, grab the latest release from the **[Releases](
### Prerequisites
- **Windows 10/11**
-- **.NET SDK** (version X.X or later — replace with your actual target)
+- **.NET SDK** (version 8.0 or later — replace with your actual target)
- **Visual Studio** (Community Edition or higher) with `.NET desktop development` workload
- *(Optional)* [Inno Setup](https://jrsoftware.org/isinfo.php) if you want to compile the installer
From 1f5786db89d4c78ac034da99a85a420c0f19be2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Fri, 19 Sep 2025 09:36:28 +0100
Subject: [PATCH 20/30] scripts: fix the compile-installer.bat file
- Leftover from initial testing. Now it does what I wanted it to do.
---
scripts/compile-installer.bat | 52 ++++++++++++++++++++++++++++-------
1 file changed, 42 insertions(+), 10 deletions(-)
diff --git a/scripts/compile-installer.bat b/scripts/compile-installer.bat
index 9384d3f..325638b 100644
--- a/scripts/compile-installer.bat
+++ b/scripts/compile-installer.bat
@@ -1,17 +1,49 @@
@ECHO off
-CALL %~dp0cleanup.bat
+SET COMPILER="%LOCALAPPDATA%\Programs\Inno Setup 6\ISCC.exe" /Q
+FOR /F "tokens=2 delims=\\" %%A IN ('whoami') DO SET USERNAME="%%A"
+SET LICENSE="%~dp0..\LICENSE"
+SET REPOSITORYDIR="%~dp0.."
+SET OUTPUTDIR="%~dp0..\build-installer"
+SET SETUPSCRIPT="%~dp0..\build-installer\wcit-setup.iss"
+SET USERNAME="felgmar"
+SET VERSION="1.0.0.0"
-dotnet publish "Windows Installer.sln" --nologo --self-contained --property:OutputPath=%~dp0..\build\ --configuration "Release"
+IF NOT EXIST %LICENSE% (
+ ECHO.ERROR: LICENSE FILE NOT FOUND
+ EXIT /B 1
+)
+
+IF NOT EXIST %OUTPUTDIR% (
+ MKDIR %OUTPUTDIR%
+)
+
+IF NOT EXIST %SETUPSCRIPT% (
+ CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0download-installer-script.ps1 -OutputPath %SETUPSCRIPT%}"
+ CALL icacls.exe "%SETUPSCRIPT%" /grant %USERNAME%:F
+)
+
+FOR %%F IN ("%OUTPUTDIR%\*.exe") DO (
+ ECHO REMOVING FILE %%F FROM %OUTPUTDIR%...
+ DEL /Q "%OUTPUTDIR%\%%F"
+)
+
+IF "%1"=="" IF NOT EXIST %SETUPSCRIPT% (
+ ECHO.ERROR: NO INNO SETUP SCRIPT WAS PROVIDED
+ EXIT /B 1
+)
-MOVE /Y %~dp0..\build\publish %~dp0..\
-RMDIR /Q /S %~dp0..\build
-MOVE /Y %~dp0..\publish %~dp0..\build
+ECHO PATCHING INNO SETUP SCRIPT...
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define RepositoryDir -Value %REPOSITORYDIR%}"
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppOutputDir -Value %OUTPUTDIR%}"
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define UserName -Value %USERNAME%}"
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppLicense -Value %LICENSE%}"
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppVersion -Value %VERSION%}"
-SET COMPILER="%LOCALAPPDATA%\Programs\Inno Setup 6\ISCC.exe"
+ECHO COMPILING INSTALLER...
+CALL %COMPILER% %SETUPSCRIPT%
-IF %1.=="" (
- echo ERROR: NO INNO SETUP SCRIPT WAS PROVIDED
-) ELSE (
- CALL %COMPILER% %1
+IF %ERRORLEVEL% NEQ 0 (
+ ECHO.COMPILATION FAILED WITH CODE %ERRORLEVEL%
+ EXIT /B %ERRORLEVEL%
)
From f569384b5038dc1db21f63fa1944cc2d84a554f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Fri, 19 Sep 2025 09:36:46 +0100
Subject: [PATCH 21/30] scripts: add AppVersion patching
---
scripts/compile-installer-cleanup.bat | 2 ++
scripts/patch-installer-script.ps1 | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/scripts/compile-installer-cleanup.bat b/scripts/compile-installer-cleanup.bat
index 4b6aeee..106d13f 100644
--- a/scripts/compile-installer-cleanup.bat
+++ b/scripts/compile-installer-cleanup.bat
@@ -9,6 +9,7 @@ SET REPOSITORYDIR="%~dp0.."
SET OUTPUTDIR="%~dp0..\build-installer"
SET SETUPSCRIPT="%~dp0..\build-installer\wcit-setup.iss"
SET USERNAME="felgmar"
+SET VERSION="1.0.0.0"
IF NOT EXIST %LICENSE% (
ECHO.ERROR: LICENSE FILE NOT FOUND
@@ -43,6 +44,7 @@ CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-sc
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppOutputDir -Value %OUTPUTDIR%}"
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define UserName -Value %USERNAME%}"
CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppLicense -Value %LICENSE%}"
+CALL powershell.exe -ExecutionPolicy Bypass -Command "& {%~dp0patch-installer-script.ps1 -OutputPath %SETUPSCRIPT% -Define AppVersion -Value %VERSION%}"
ECHO COMPILING INSTALLER...
CALL %COMPILER% %SETUPSCRIPT%
diff --git a/scripts/patch-installer-script.ps1 b/scripts/patch-installer-script.ps1
index 95a0042..3e42950 100644
--- a/scripts/patch-installer-script.ps1
+++ b/scripts/patch-installer-script.ps1
@@ -3,7 +3,7 @@ param (
[Parameter(Mandatory = $true)]
[string]$OutputPath,
[Parameter(Mandatory = $true)]
- [ValidateSet('AppOutputDir', 'UserName', 'AppLicense', 'RepositoryDir')]
+ [ValidateSet('AppOutputDir', 'UserName', 'AppLicense', 'RepositoryDir', 'AppVersion')]
[String]$Define,
[Parameter(Mandatory = $true)]
[String]$Value
From 0b4431c3c4b1d29ce9667213fc2d4c8cd50db157 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Fri, 19 Sep 2025 09:40:13 +0100
Subject: [PATCH 22/30] README: fix typos and wording
- Yes I have used AI. This would have been a repetitive task otherwise.
---
README.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index d05e963..e895e92 100644
--- a/README.md
+++ b/README.md
@@ -36,12 +36,12 @@ All build scripts are in the root folder. Run them from **Command Prompt** or **
| `build.bat` | Builds the project in Release mode |
| `build-clean.bat` | Cleans previous build artifacts, then builds |
| `publish.bat` | Publishes the project (self-contained build) |
-| `publish-cleanup.bat` | Cleans, then publishes |
-| `compile.bat` | Compiles without publishing |
-| `compile-installer.bat` | Builds and packages an installer using Inno Setup |
+| `publish-cleanup.bat` | Cleans, then publishes the project |
+| `compile-installer.bat` | Compiles without publishing |
+| `compile-installer-cleanup.bat` | Cleans, then compiles without publishing |
| `cleanup.bat` | Removes build artifacts |
-| `patch-installer-scripts.ps1` | Updates installer scripts before compiling |
-| `installer-scripts.ps1` | Helper script for installer creation |
+| `patch-installer-script.ps1` | Patches the installer script before compiling |
+| `download-installer-script.ps1` | Helper script for downloading the installer script |
---
From 591ec6578597565471e5575d110894435170dabf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Wed, 24 Sep 2025 20:27:32 +0100
Subject: [PATCH 23/30] WindowsInstallerLib: bump version to 1.1.3.0
---
WindowsInstallerLib/WindowsInstallerLib.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/WindowsInstallerLib/WindowsInstallerLib.csproj b/WindowsInstallerLib/WindowsInstallerLib.csproj
index 0bfcb28..f03de5a 100644
--- a/WindowsInstallerLib/WindowsInstallerLib.csproj
+++ b/WindowsInstallerLib/WindowsInstallerLib.csproj
@@ -8,7 +8,7 @@
none
$(AssemblyName)
latest
- 1.1.2.2
+ 1.1.3.0
x64
true
From 6fbdf766d8e9a29f8c42b310e7dc9aeaf75236e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Thu, 30 Oct 2025 15:42:45 +0000
Subject: [PATCH 24/30] WindowsInstallerLib: update depencies
Upgraded Microsoft.Dism to 3.3.12 and System.Management to 9.0.10 to ensure compatibility with latest features and bug fixes.
---
WindowsInstallerLib/WindowsInstallerLib.csproj | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/WindowsInstallerLib/WindowsInstallerLib.csproj b/WindowsInstallerLib/WindowsInstallerLib.csproj
index f03de5a..bae29e8 100644
--- a/WindowsInstallerLib/WindowsInstallerLib.csproj
+++ b/WindowsInstallerLib/WindowsInstallerLib.csproj
@@ -27,8 +27,8 @@
-
-
+
+
From 9bc4694dc579c693a9de60c161cc0512d74b672e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Tue, 18 Nov 2025 14:24:00 +0000
Subject: [PATCH 25/30] Upgrade to .NET 10.0 (#44)
* WindowsInstallerLib: bump TargetFramework to .NET 10.0
* ConsoleApp: bump TargetFramework to .NET 10.0
* WindowsInstallerLib: bump version to 1.1.4.0
* ConsoleApp: bump version to 0.0.7.0
---
ConsoleApp/ConsoleApp.csproj | 4 ++--
WindowsInstallerLib/WindowsInstallerLib.csproj | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/ConsoleApp/ConsoleApp.csproj b/ConsoleApp/ConsoleApp.csproj
index 0bea368..da2b86d 100644
--- a/ConsoleApp/ConsoleApp.csproj
+++ b/ConsoleApp/ConsoleApp.csproj
@@ -3,7 +3,7 @@
wcit
Exe
- net9.0-windows
+ net10.0-windows
enable
x64
true
@@ -12,7 +12,7 @@
none
latest-recommended
ConsoleApp.Program
- 0.0.6.3
+ 0.0.7.0
true
app.manifest
diff --git a/WindowsInstallerLib/WindowsInstallerLib.csproj b/WindowsInstallerLib/WindowsInstallerLib.csproj
index bae29e8..3969d30 100644
--- a/WindowsInstallerLib/WindowsInstallerLib.csproj
+++ b/WindowsInstallerLib/WindowsInstallerLib.csproj
@@ -1,14 +1,14 @@
- net9.0-windows
+ net10.0-windows
enable
x64
True
none
$(AssemblyName)
latest
- 1.1.3.0
+ 1.1.4.0
x64
true
From 18748b79c0fe61dbbd017c05d6ee3ed941787386 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Tue, 18 Nov 2025 14:32:49 +0000
Subject: [PATCH 26/30] scripts: bump VERSION to the latest release
---
scripts/compile-installer-cleanup.bat | 2 +-
scripts/compile-installer.bat | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/compile-installer-cleanup.bat b/scripts/compile-installer-cleanup.bat
index 106d13f..791189e 100644
--- a/scripts/compile-installer-cleanup.bat
+++ b/scripts/compile-installer-cleanup.bat
@@ -9,7 +9,7 @@ SET REPOSITORYDIR="%~dp0.."
SET OUTPUTDIR="%~dp0..\build-installer"
SET SETUPSCRIPT="%~dp0..\build-installer\wcit-setup.iss"
SET USERNAME="felgmar"
-SET VERSION="1.0.0.0"
+SET VERSION="1.0.1.0"
IF NOT EXIST %LICENSE% (
ECHO.ERROR: LICENSE FILE NOT FOUND
diff --git a/scripts/compile-installer.bat b/scripts/compile-installer.bat
index 325638b..3391bf9 100644
--- a/scripts/compile-installer.bat
+++ b/scripts/compile-installer.bat
@@ -7,7 +7,7 @@ SET REPOSITORYDIR="%~dp0.."
SET OUTPUTDIR="%~dp0..\build-installer"
SET SETUPSCRIPT="%~dp0..\build-installer\wcit-setup.iss"
SET USERNAME="felgmar"
-SET VERSION="1.0.0.0"
+SET VERSION="1.0.1.0"
IF NOT EXIST %LICENSE% (
ECHO.ERROR: LICENSE FILE NOT FOUND
From dd7aff3443bd10f9fdf519d15d9ebda8fc282f64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 24 Nov 2025 20:38:36 +0000
Subject: [PATCH 27/30] ConsoleApp: throw exception for unknown command-line
arguments
Added a default case to ArgumentParser to throw an ArgumentException when an unknown argument is encountered, improving error handling and user feedback.
---
ConsoleApp/src/ArgumentParser.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ConsoleApp/src/ArgumentParser.cs b/ConsoleApp/src/ArgumentParser.cs
index c6bafca..7deb9bb 100644
--- a/ConsoleApp/src/ArgumentParser.cs
+++ b/ConsoleApp/src/ArgumentParser.cs
@@ -64,6 +64,12 @@ internal static void ParseArgs(ref Parameters parameters, string[] args)
case "/firmwaretype":
parameters.FirmwareType = args[Array.IndexOf(args, arg) + 1].ToUpperInvariant();
continue;
+ default:
+ if (arg.StartsWith("/", StringComparison.CurrentCulture) || !string.IsNullOrWhiteSpace(arg))
+ {
+ throw new ArgumentException($"Unknown argument: {arg}");
+ }
+ break;
}
}
#if DEBUG
From 0b8e9afb023e68c1585f11feda7f13eb3371109d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 24 Nov 2025 20:39:29 +0000
Subject: [PATCH 28/30] WindowsInstallerLib: ensure DismSession is properly
closed
Added null checks and safe closing of DismSession objects in multiple methods to prevent potential null reference exceptions and resource leaks. Improved error handling in AddDrivers by logging DismException details.
---
WindowsInstallerLib/src/DeployManager.cs | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/WindowsInstallerLib/src/DeployManager.cs b/WindowsInstallerLib/src/DeployManager.cs
index 903c220..64e4d42 100644
--- a/WindowsInstallerLib/src/DeployManager.cs
+++ b/WindowsInstallerLib/src/DeployManager.cs
@@ -77,6 +77,7 @@ internal static void AddDriver(ref Parameters parameters,
}
finally
{
+ session?.Close();
DismApi.Shutdown();
}
}
@@ -96,9 +97,9 @@ internal static void AddDriver(ref Parameters parameters,
/// Thrown if the directory specified in does not exist.
/// Thrown if the current process does not have administrative privileges required to initialize the DISM API.
internal static void AddDrivers(ref Parameters parameters,
- string DriversSource,
- bool ForceUnsigned = false,
- bool Recursive = false)
+ string DriversSource,
+ bool ForceUnsigned = false,
+ bool Recursive = false)
{
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.ImageFilePath, nameof(parameters.ImageFilePath));
ArgumentException.ThrowIfNullOrWhiteSpace(DriversSource, nameof(DriversSource));
@@ -120,9 +121,9 @@ internal static void AddDrivers(ref Parameters parameters,
{
DismApi.InitializeEx(DismLogLevel.LogErrorsWarnings);
}
- catch (DismException)
+ catch (DismException ex)
{
- throw;
+ Console.Write($"An error occured: {ex}");
}
catch (Exception)
{
@@ -140,6 +141,7 @@ internal static void AddDrivers(ref Parameters parameters,
}
finally
{
+ session?.Close();
DismApi.Shutdown();
}
}
@@ -335,7 +337,7 @@ internal static DismImageInfoCollection GetImageInfoT(ref Parameters parameters)
///
internal static void InstallAdditionalDrivers(ref Parameters parameters)
{
- DismSession session;
+ DismSession? session = null;
ArgumentException.ThrowIfNullOrWhiteSpace(parameters.AdditionalDriversDrive, nameof(parameters));
@@ -355,7 +357,7 @@ internal static void InstallAdditionalDrivers(ref Parameters parameters)
throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
}
- session = DismApi.OpenOfflineSession(parameters.DestinationDrive);
+ session ??= DismApi.OpenOfflineSession(parameters.DestinationDrive);
}
catch (DismException)
@@ -373,7 +375,7 @@ internal static void InstallAdditionalDrivers(ref Parameters parameters)
}
finally
{
- session.Close();
+ session?.Close();
DismApi.Shutdown();
}
}
From 86d72febab6f07cb11462bd13354b60a2ad4c5f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 22 Dec 2025 20:46:30 +0000
Subject: [PATCH 29/30] WindowsInstallerLib: add exception handling to DISM API
Wrapped DismApi.Initialize and DismApi.OpenOfflineSession calls in try-catch blocks to handle DismException and general exceptions explicitly. This improves robustness and error reporting during deployment operations.
---
WindowsInstallerLib/src/DeployManager.cs | 27 +++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/WindowsInstallerLib/src/DeployManager.cs b/WindowsInstallerLib/src/DeployManager.cs
index 64e4d42..1b9e272 100644
--- a/WindowsInstallerLib/src/DeployManager.cs
+++ b/WindowsInstallerLib/src/DeployManager.cs
@@ -351,14 +351,35 @@ internal static void InstallAdditionalDrivers(ref Parameters parameters)
switch (PrivilegesManager.IsAdmin())
{
case true:
- DismApi.Initialize(DismLogLevel.LogErrorsWarnings);
+ try
+ {
+ DismApi.Initialize(DismLogLevel.LogErrorsWarnings);
+ }
+ catch (DismException)
+ {
+ throw;
+ }
+ catch (Exception)
+ {
+ throw;
+ }
break;
case false:
throw new UnauthorizedAccessException("You do not have enough privileges to initialize the DISM API.");
}
- session ??= DismApi.OpenOfflineSession(parameters.DestinationDrive);
-
+ try
+ {
+ session ??= DismApi.OpenOfflineSession(parameters.DestinationDrive);
+ }
+ catch (DismException)
+ {
+ throw;
+ }
+ catch (Exception)
+ {
+ throw;
+ }
}
catch (DismException)
{
From f19cbf17f94c0955ba9acf7e7df7aa8b0c438d48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felipe=20Gonz=C3=A1lez=20Mart=C3=ADn?=
<158048821+felgmar@users.noreply.github.com>
Date: Mon, 22 Dec 2025 20:46:44 +0000
Subject: [PATCH 30/30] WindowsInstallerLib: improve drive validation and error
handling
Refined validation logic for destination drive format, ensuring it is exactly two characters and ends with a colon. Enhanced error handling in deployment steps to log exceptions with descriptive messages instead of rethrowing, improving user feedback during installation.
---
WindowsInstallerLib/src/InstallerManager.cs | 31 ++++++++++-----------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/WindowsInstallerLib/src/InstallerManager.cs b/WindowsInstallerLib/src/InstallerManager.cs
index 1657743..5107214 100644
--- a/WindowsInstallerLib/src/InstallerManager.cs
+++ b/WindowsInstallerLib/src/InstallerManager.cs
@@ -91,13 +91,16 @@ public static void Configure(ref Parameters parameters)
ArgumentException.ThrowIfNullOrWhiteSpace(p_DestinationDrive);
- if (p_DestinationDrive.StartsWith(':'))
+ if (p_DestinationDrive.Length != 2 ||
+ p_DestinationDrive.Length > 2)
{
- throw new ArgumentException(@$"Invalid source drive {p_DestinationDrive}, it must have a colon at the end not at the beginning. For example: 'Z:'.");
+ throw new ArgumentException(@$"Invalid source drive {p_DestinationDrive}. Too many characters.");
}
- else if (!p_DestinationDrive.EndsWith(':'))
+
+ if (p_DestinationDrive.StartsWith(':') ||
+ !p_DestinationDrive.EndsWith(':'))
{
- throw new ArgumentException($"Invalid source drive {p_DestinationDrive}, it must have a colon. For example: 'Z:'.");
+ throw new InvalidDataException(@$"Invalid source drive {p_DestinationDrive}. A valid drive is for example: 'Z:'.");
}
parameters.DestinationDrive = p_DestinationDrive;
@@ -299,10 +302,6 @@ public static void Configure(ref Parameters parameters)
switch (UserWantsExtraDrivers.ToLower())
{
- case "no":
- return;
- case "n":
- break;
case "yes":
case "y":
Console.Write("\n==> Specify the directory where the drivers are located. (e.g. X:\\Drivers): ");
@@ -362,36 +361,36 @@ public static void InstallWindows(ref Parameters parameters)
{
DiskManager.FormatDisk(ref parameters);
}
- catch (Exception)
+ catch (Exception ex)
{
- throw;
+ Console.WriteLine($"An error occurred while formatting the disk: {ex}", ConsoleColor.Red);
}
try
{
DeployManager.ApplyImage(ref parameters);
}
- catch (Exception)
+ catch (Exception ex)
{
- throw;
+ Console.WriteLine($"An error occurred while applying the image: {ex}", ConsoleColor.Red);
}
try
{
DeployManager.InstallAdditionalDrivers(ref parameters);
}
- catch (Exception)
+ catch (Exception ex)
{
- throw;
+ Console.WriteLine($"An error occurred when installing additional drivers: {ex}", ConsoleColor.Yellow);
}
try
{
DeployManager.InstallBootloader(ref parameters);
}
- catch (Exception)
+ catch (Exception ex)
{
- throw;
+ Console.WriteLine($"An error occurred while installing the bootloader: {ex}", ConsoleColor.Red);
}
}
}