Files
Flyoobe/Flyoobe/UpgradeView/IsoHandler.cs
T
2025-09-04 18:10:14 +02:00

451 lines
19 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Net.WebRequestMethods;
namespace Flyoobe
{
public class IsoHandler
{
private static readonly HttpClient httpClient = new HttpClient();
private readonly Action<string> _updateStatus;
public IsoHandler(Action<string> updateStatus)
{
_updateStatus = updateStatus;
}
public async Task HandleIso(string isoPath, bool experimentalEnabled)
{
try
{
// Block Win10 ISOs
string isoFileName = Path.GetFileName(isoPath);
if (Regex.IsMatch(isoFileName, @"(?i)(win10|windows\s*10)"))
{
_updateStatus("Error: Windows 10 ISOs are not supported.");
MessageBox.Show("Windows 10 ISOs are not supported. Please use a Windows 11 ISO.",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
_updateStatus("Mounting the ISO...");
// Ensure the ISO path is properly quoted
string quotedIsoPath = $"\"{isoPath}\"";
// Pass the quoted path to ExecutePowerShellCommand
string driveLetter = await MountIsoAndGetDriveLetter(isoPath);
if (string.IsNullOrEmpty(driveLetter))
{
HandleException(new InvalidOperationException("ISO mounting returned no drive letter."),
"Failed to mount the ISO. Please try again.");
return;
}
_updateStatus("ISO mounted successfully! Let's get this Windows 11 ready!");
string setupPath = Path.Combine(driveLetter, "sources", "setupprep.exe");
if (System.IO.File.Exists(setupPath))
{
await RunSetupWithAdminRights(setupPath, experimentalEnabled);
}
else
{
HandleException(new FileNotFoundException($"Setup file not found in {driveLetter}."),
"The setup file could not be found on the mounted ISO.");
}
}
catch (Exception ex)
{
_updateStatus($"Oops! Something went wrong: {ex.Message}");
HandleException(ex, "An error occurred during ISO processing.");
}
}
private async Task<string> MountIsoAndGetDriveLetter(string isoPath)
{
try
{
_updateStatus("Mounting ISO...");
string script = $@"
try {{
$iso = Mount-DiskImage -ImagePath '{isoPath}' -PassThru -ErrorAction Stop
$volumes = Get-Volume -DiskImage $iso -ErrorAction Stop
# get drive letter, but only if one exists
$driveLetter = ($volumes | Where-Object {{ $_.DriveLetter }} | Select-Object -First 1).DriveLetter
if (-not $driveLetter) {{
throw 'iso mounted but no drive letter was assigned.'
}}
# return the drive letter (e.g. 'D:')
$driveLetter
}} catch {{
Write-Error $_.Exception.Message
exit 1 # indicate an error
}}
";
using (var process = new Process())
{
process.StartInfo.FileName = "powershell.exe";
process.StartInfo.Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"{script}\"";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.Start();
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
if (process.ExitCode != 0 || !string.IsNullOrWhiteSpace(error))
{
_updateStatus($"PowerShell error during mount: {error.Trim()}");
Logger.Log($"PowerShell error during mount: {error.Trim()}", LogLevel.Error);
MessageBox.Show($"PowerShell error during mount: {error.Trim()}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
string driveLetter = output.Trim();
if (!string.IsNullOrWhiteSpace(driveLetter))
{
// Ensure the drive letter format is correct (e.g., "D:")
if (!driveLetter.EndsWith(":") && driveLetter.Length == 1)
{
driveLetter += ":";
}
_updateStatus($"ISO mounted successfully at {driveLetter}\\");
return $"{driveLetter}\\";
}
else
{
_updateStatus("Failed to retrieve drive letter after mounting ISO. Output was empty.");
Logger.Log("Failed to retrieve drive letter after mounting ISO. Output was empty.", LogLevel.Error);
return null;
}
}
}
catch (Exception ex)
{
_updateStatus("Exception during mounting: " + ex.Message);
HandleException(ex, "An error occurred while mounting the ISO.");
return null;
}
}
private async Task RunSetupWithAdminRights(string setupPath, bool experimentalEnabled)
{
try
{
_updateStatus("Starting the setup process with elevated privileges...");
// Default argument
string arguments = "/Product Server";
// If experimental mode is enabled, append additional setup parameters
if (experimentalEnabled)
{
arguments += " /Compat IgnoreWarning /MigrateDrivers All";
}
var startInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"Start-Process '{setupPath}' -ArgumentList '{arguments}' -Verb runas\"",
Verb = "runas",
UseShellExecute = true,
CreateNoWindow = false
};
_updateStatus("Almost there! We're getting the setup ready...");
using (var process = Process.Start(startInfo))
{
if (process != null)
{
await process.WaitForExitAsync();
}
}
// Final status message
string finalStatus = "You're ready to install Windows 11 on unsupported hardware! Ignore the 'Windows Server' prompt; you're all set!";
if (experimentalEnabled)
{
finalStatus += " (Advanced mode enabled: Compatibility checks bypassed)";
}
_updateStatus(finalStatus);
MessageBox.Show("Windows 11 installation can now proceed. Please follow the instructions in the setup window.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
_updateStatus($"Error: {ex.Message}");
MessageBox.Show($"Error: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// Create unattend.xml file in the sources\$OEM$\$$\Panther directory
/// </summary>
/// <param name="usbDrive">The USB drive where the unattend.xml will be created.</param>
public void CreateUnattendXml(string usbDrive)
{
string unattendDir = Path.Combine(usbDrive, "sources", "$OEM$", "$$", "Panther");
Directory.CreateDirectory(unattendDir); // Create the directory if it doesn't exist
string unattendPath = Path.Combine(unattendDir, "unattend.xml");
// Check if the file already exists
if (!System.IO.File.Exists(unattendPath))
{
// Create the unattend.xml content
string xmlContent = @"<unattend xmlns='urn:schemas-microsoft-com:unattend'>
<settings pass='disabled'>
<component xmlns:wcm='http://schemas.microsoft.com/WMIConfig/2002/State' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' name='Microsoft-Windows-Setup' processorArchitecture='amd64' language='neutral' publicKeyToken='31bf3856ad364e35' versionScope='nonSxS'>
<UserData>
<ProductKey>
<Key/>
</ProductKey>
</UserData>
<RunSynchronous>
<RunSynchronousCommand wcm:action='add'>
<Order>1</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action='add'>
<Order>2</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action='add'>
<Order>3</Order>
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass='specialize'>
<component xmlns:wcm='http://schemas.microsoft.com/WMIConfig/2002/State' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' name='Microsoft-Windows-Deployment' processorArchitecture='amd64' language='neutral' publicKeyToken='31bf3856ad364e35' versionScope='nonSxS'>
<RunSynchronous>
<RunSynchronousCommand wcm:action='add'>
<Order>1</Order>
<Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
</unattend>";
System.IO.File.WriteAllText(unattendPath, xmlContent);
_updateStatus("Created unattend.xml in the sources\\$OEM$\\$$\\Panther directory.");
}
else
{
_updateStatus("unattend.xml already exists in the sources\\$OEM$\\$$\\Panther directory.");
// Display a message box to inform the user
MessageBox.Show("The patch has already been applied. The 'unattend.xml' file already exists in the specified directory.",
"Patch Already Applied",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
/// <summary>
/// Handles the event for the compatibility patch.
/// </summary>
public void HandleCompatibilityPatch()
{
using (var folderDialog = new FolderBrowserDialog())
{
/* Select the USB drive containing your Windows 11 installation files. " +
"This feature adds a compatibility patch to bypass certain system requirements. " +
"Compatible with drives prepared by any tool, including Rufus. Ensure the drive is ready! */
folderDialog.Description = "Select the USB drive containing your Windows 11 installation files.\n" +
"This feature adds a compatibility patch to bypass certain system requirements.\n" +
"Compatible with drives prepared by any tool, including Rufus. Ensure the drive is ready!";
if (folderDialog.ShowDialog() == DialogResult.OK)
{
string selectedPath = folderDialog.SelectedPath;
var driveInfo = new DriveInfo(Path.GetPathRoot(selectedPath));
if (driveInfo.DriveType == DriveType.Removable && driveInfo.IsReady)
{
if (MessageBox.Show("This will apply compatibility bypass settings on the selected USB drive. Continue ?",
"Apply Bypass Patch", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
{
try
{
CreateUnattendXml(selectedPath);
MessageBox.Show("Bypass patch applied successfully!", "Apply Bypass Patch", MessageBoxButtons.OK, MessageBoxIcon.Information);
Logger.Log("Bypass patch applied successfully!");
}
catch (Exception ex)
{
Logger.Log($"{"Failed to apply bypass patch:"} {ex.Message}", LogLevel.Error);
}
}
else
{
Logger.Log("Bypass patch canceled by user.", LogLevel.Warning);
}
}
else
{
MessageBox.Show("The selected path is not a removable drive. Please select a USB drive.", "Invalid Selection", MessageBoxButtons.OK, MessageBoxIcon.Error);
Logger.Log("User attempted to select a non-removable drive.", LogLevel.Error);
}
}
else
{
Logger.Log("No USB drive selected for patching.", LogLevel.Error);
}
}
}
/// <summary>
/// Handles the process of downloading the Media Creation Tool and optionally running it.
/// </summary>
public void HandleMediaCreationToolDownload()
{
var infoResult = MessageBox.Show(
"With the Media Creation Tool, you can upgrade your PC or create an ISO.\n\nIn the 'Choose which media to use' step, select 'ISO file'.\n\nWould you like to watch a tutorial first?",
"Media Creation Tool",
MessageBoxButtons.YesNo,
MessageBoxIcon.Information);
if (infoResult == DialogResult.Yes)
{
Process.Start("https://www.youtube.com/watch?v=dNl1FHajW9w");
}
using (var saveDialog = new SaveFileDialog())
{
saveDialog.Title = "Save Media Creation Tool";
saveDialog.Filter = "Executable|*.exe";
saveDialog.FileName = "MediaCreationTool.exe";
if (saveDialog.ShowDialog() == DialogResult.OK)
{
using (var client = new WebClient())
{
try
{
client.DownloadFile("https://go.microsoft.com/fwlink/?linkid=2156295", saveDialog.FileName);
MessageBox.Show("Tool downloaded successfully!", "Done", MessageBoxButtons.OK, MessageBoxIcon.Information);
var run = MessageBox.Show("Do you want to run it now?", "Run Tool", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (run == DialogResult.Yes)
{
Process.Start(saveDialog.FileName);
}
_updateStatus("Media Creation Tool downloaded and optionally launched.");
}
catch (Exception ex)
{
_updateStatus("Failed to download Media Creation Tool: " + ex.Message);
MessageBox.Show("Download failed: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
Logger.Log($"Failed to download Media Creation Tool: {ex.Message}", LogLevel.Error);
}
}
}
else
{
_updateStatus("Media Creation Tool download canceled.");
}
}
}
/// <summary>
/// Downloads and launches the Fido PowerShell script for advanced ISO selection.
/// </summary>
public void DownloadAndRunFidoScript(string scriptUrl)
{
string tempScript = Path.Combine(Path.GetTempPath(), "Fido.ps1");
using (var client = new WebClient())
{
try
{
client.DownloadFile(scriptUrl, tempScript);
_updateStatus("Fido script downloaded successfully. Launching PowerShell...");
Process.Start(new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-ExecutionPolicy Bypass -File \"{tempScript}\"",
Verb = "runas",
UseShellExecute = true
});
}
catch (Exception ex)
{
_updateStatus("Error downloading or running Fido script: " + ex.Message);
Logger.Log($"Error downloading or running Fido script: {ex.Message}", LogLevel.Error);
}
}
}
/// <summary>
/// Handle unexpected exceptions by logging the error, updating the status, and optionally launching Copilot for help.
/// </summary>
/// <param name="ex"></param>
/// <param name="userContext"></param>
private void HandleException(Exception ex, string userContext = "An unexpected error occurred.")
{
string fullMessage = $"{userContext}\n\n{ex.Message}\n\nThe error has been copied to your clipboard.";
// Clipboard sicher setzen über STA-Thread
Thread clipboardThread = new Thread(() => Clipboard.SetText(ex.ToString()));
clipboardThread.SetApartmentState(ApartmentState.STA);
clipboardThread.Start();
clipboardThread.Join();
_updateStatus(userContext);
Logger.Log($"{userContext}: {ex}", LogLevel.Error);
var result = MessageBox.Show(
fullMessage + "\n\nWould you like to ask Copilot for help?",
"Something Went Wrong",
MessageBoxButtons.YesNo,
MessageBoxIcon.Error);
if (result == DialogResult.Yes)
{
CopilotHelper.LaunchCopilot();
}
}
}
public static class ProcessExtensions
{
public static async Task WaitForExitAsync(this Process process)
{
var tcs = new TaskCompletionSource<bool>();
process.EnableRaisingEvents = true;
process.Exited += (sender, args) => tcs.TrySetResult(true);
await tcs.Task;
}
}
}