Almost finalized

This commit is contained in:
2025-10-24 19:10:40 +02:00
parent 3493a61317
commit e762247a2a
12 changed files with 116 additions and 223 deletions

View File

@@ -9,13 +9,12 @@ using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.Threading;
using DiscUtils.Udf;
using System.Windows.Forms.Design;
namespace Perfect11
{
@@ -497,11 +496,7 @@ namespace Perfect11
bypassWin11RequirementsCheck.Enabled = false;
bypassWin11RequirementsCheck.Checked = false;
}
else if (upgradeMethod.SelectedIndex == 1) // Windows 11 LTSC
{
automateOOBECheck.Enabled = true;
}
if (upgradeMethod.SelectedIndex != 0) // if not normal installation
if (upgradeMethod.SelectedIndex != 0 && upgradeMethod.SelectedIndex != 1) // if not normal installation
{
bypassWin11RequirementsCheck.Enabled = false;
bypassWin11RequirementsCheck.Checked = false;
@@ -524,7 +519,6 @@ namespace Perfect11
{
url = "https://archive.org/download/mini11-24h2/Mini11%20LTS%2024H2%20AIO%20v1%20Triton.iso";
}
#if !DEBUG
// Se stiamo già scaricando → annulla
if (_isDownloading)
{
@@ -548,6 +542,10 @@ namespace Perfect11
_cts = new CancellationTokenSource();
if (!Directory.Exists("C:\\Temp"))
Directory.CreateDirectory("C:\\Temp");
if (Directory.Exists("C:\\Temp\\Perfect11_W11_TMP"))
Directory.Delete("C:\\Temp\\Perfect11_W11_TMP",true);
if (File.Exists("C:\\Temp\\windows.iso"))
File.Delete("C:\\Temp\\windows.iso");
if (upgradeMethod.SelectedIndex != 3)
{
destination = Path.Combine(@"C:\Temp", @"windows.iso");
@@ -557,36 +555,42 @@ namespace Perfect11
}
Thread.Sleep(1000);
statusLabel.Text = "Extracting...";
await Task.Run(() => ExtractIsoWithProgress(destination, Path.Combine(@"C:\Temp","Perfect11_W11_TMP"), _cts.Token));
await Task.Run(() => ExtractIsoWithProgress(destination, @"C:\Temp\Perfect11_W11_TMP", _cts.Token));
statusLabel.Text = "Extraction complete!";
#if !DEBUG
if (File.Exists(destination) && upgradeMethod.SelectedIndex != 3)
File.Delete(destination);
#endif
if (!File.Exists(@"C:\Temp\Perfect11_W11_TMP\sources\EI.cfg"))
{
File.WriteAllText(@"C:\Temp\Perfect11_W11_TMP\sources\EI.CFG",Resources.EI_CFG);
}
Thread.Sleep(1000);
if (automateOOBECheck.Checked)
{
statusLabel.Text = "Applying OOBE automation tweak...";
if (!Directory.Exists(Path.Combine(@"C:\Temp", @"Perfect11_W11_TMP\sources\$OEM$\$$\Panther")))
if (File.Exists(@"C:\Temp\Perfect11_W11_TMP\autounattend.xml"))
{
Directory.CreateDirectory(Path.Combine(@"C:\Temp", @"Perfect11_W11_TMP\sources\$OEM$\$$\Panther"));
File.Delete(@"C:\Temp\Perfect11_W11_TMP\autounattend.xml");
}
if (File.Exists(Path.Combine(@"C:\Temp", @"Perfect11_W11_TMP\sources\$OEM$\$$\Panther\unattend.xml")))
{
File.Delete(Path.Combine(@"C:\Temp", @"Perfect11_W11_TMP\sources\$OEM$\$$\Panther\unattend.xml"));
File.WriteAllText(@"C:\Temp\Perfect11_W11_TMP\autounattend.xml",Resources.unattend_OOBEAutomate);
}
File.WriteAllText(Path.Combine(@"C:\Temp", @"Perfect11_W11_TMP\sources\$OEM$\$$\Panther\unattend.xml"),Resources.unattend_OOBEAutomate);
}
statusLabel.Text = "Checking Windows 11 Setup... (ignore the Windows Server title and texts)";
statusLabel.Text = "Running Windows 11 Setup...";
if (bypassWin11RequirementsCheck.Checked)
{
setupArguments += "/Product Server /Compat IgnoreWarning /MigrateDrivers All";
setupArguments += "/Product Server";
}
if (upgradeMethod.SelectedIndex == 2)
{
setupArguments += "/pkey KBN8V-HFGQ4-MGXVD-347P6-PDQGT";
}
setupArguments += " /Compat IgnoreWarning /MigrateDrivers All /Telemetry disable /eula accept /dynamicupdate enable";
ProcessStartInfo info = new ProcessStartInfo
{
FileName = Path.Combine(@"C:\Temp", @"Perfect11_W11_TMP\sources\setupprep.exe"),
FileName = @"C:\Temp\Perfect11_W11_TMP\sources\setupprep.exe",
Arguments = setupArguments
};
using (var process = Process.Start(info))
{
process?.WaitForExit();
}
Process.Start(info);
_cts = null;
MessageBox.Show("Almost done! Now follow the prompts to continue the Windows 11 installation! Perfect11 will now close.","Perfect11",MessageBoxButtons.OK,MessageBoxIcon.Information);
Application.Exit();
@@ -596,10 +600,12 @@ namespace Perfect11
statusLabel.Text = "Installation aborted!";
try
{
if (File.Exists(destination))
#if !DEBUG
if (File.Exists(destination) && upgradeMethod.SelectedIndex != 3)
File.Delete(destination);
if (Directory.Exists(Path.Combine(@"C:\Temp", "Perfect11_W11_TMP")))
Directory.Delete(Path.Combine(@"C:\Temp", "Perfect11_W11_TMP"), true);
#endif
}
catch { }
}
@@ -608,10 +614,12 @@ namespace Perfect11
statusLabel.Text = "Error: " + ex.Message;
try
{
if (File.Exists(destination))
#if !DEBUG
if (File.Exists(destination) && upgradeMethod.SelectedIndex != 3)
File.Delete(destination);
if (Directory.Exists(Path.Combine(@"C:\Temp", "Perfect11_W11_TMP")))
Directory.Delete(Path.Combine(@"C:\Temp", "Perfect11_W11_TMP"), true);
#endif
} catch { }
}
finally
@@ -627,11 +635,7 @@ namespace Perfect11
bypassWin11RequirementsCheck.Enabled = false;
bypassWin11RequirementsCheck.Checked = false;
}
else if (upgradeMethod.SelectedIndex == 1) // Windows 11 LTSC
{
automateOOBECheck.Enabled = true;
}
if (upgradeMethod.SelectedIndex != 0) // if not normal installation
if (upgradeMethod.SelectedIndex != 0 && upgradeMethod.SelectedIndex != 1) // if not normal installation
{
bypassWin11RequirementsCheck.Enabled = false;
bypassWin11RequirementsCheck.Checked = false;
@@ -644,9 +648,6 @@ namespace Perfect11
}
_cts = null;
}
#else
MessageBox.Show(destination + " " + url, "Perfect11");
#endif
}
public async Task DownloadFileAsync(string url, string destinationPath, CancellationToken token)
{
@@ -684,194 +685,66 @@ namespace Perfect11
}
}
}
// Assicurati di avere:
// using System.Text;
private void ExtractIsoWithProgress(string isoPath, string extractPath, CancellationToken token)
private async Task ExtractIsoWithProgress(string isoPath, string extractPath, CancellationToken token)
{
// Log file per diagnosticare i file che falliscono
string logPath = Path.Combine(Path.GetDirectoryName(isoPath) ?? ".", "extract_errors.log");
File.WriteAllText(logPath, $"Extraction started: {DateTime.Now}\r\n");
string sevenZipPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), @"Tools\7z.exe");
if (!File.Exists(sevenZipPath))
throw new FileNotFoundException("7-Zip not found.");
using (FileStream isoStream = File.OpenRead(isoPath))
ProcessStartInfo psi = new ProcessStartInfo
{
object reader = null;
bool isUdf = false;
FileName = sevenZipPath,
Arguments = $"x -o\"{extractPath}\" -y -bsp1 -bso1 \"{isoPath}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
// Provo prima UDF, poi ISO9660/Joliet (CDReader)
try
using (Process proc = new Process { StartInfo = psi })
{
reader = new DiscUtils.Udf.UdfReader(isoStream);
isUdf = true;
}
catch (Exception exUdf)
{
try
{
// rewind stream and try CDReader
isoStream.Seek(0, SeekOrigin.Begin);
reader = new DiscUtils.Iso9660.CDReader(isoStream, true);
isUdf = false;
}
catch (Exception exCd)
{
File.AppendAllText(logPath, $"Failed to open ISO with Udf ({exUdf.Message}) and CDReader ({exCd.Message})\r\n");
throw new Exception("Unable to open ISO with UDF or ISO9660 readers. See log for details.");
}
}
proc.Start();
// Ottieni file list (entrambi supportano GetFiles con stessa signature)
var allFiles = (reader as dynamic).GetFiles("", "*", SearchOption.AllDirectories) as IEnumerable<string>;
var filesList = allFiles.ToList();
int totalFiles = filesList.Count;
int extractedCount = 0;
string line;
int lastPercent = 0;
var regex = new Regex(@"(\d+)%");
// Imposta progress bar
Invoke(new Action(() =>
{
installProgress.Maximum = Math.Max(1, totalFiles);
installProgress.Value = 0;
statusLabel.Text = "Extracting files...";
}));
foreach (string rawFile in filesList)
await Task.Run(() =>
{
while (!proc.HasExited)
{
token.ThrowIfCancellationRequested();
string sourcePath = rawFile; // il percorso così com'è per il reader
// Costruiamo il percorso locale su disco (convertendo slash)
string localRelative = sourcePath.TrimStart('/', '\\').Replace('/', Path.DirectorySeparatorChar);
string destination = Path.Combine(extractPath, localRelative);
try
{
// Crea directory di destinazione
string dir = Path.GetDirectoryName(destination);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
// Proviamo ad aprire il file con varie strategie
Stream srcStream = TryOpenFileWithVariants(reader, sourcePath, logPath);
if (srcStream == null)
{
// se non siamo riusciti ad aprire, logga e continua
File.AppendAllText(logPath, $"[SKIP] Could not open: '{sourcePath}'\r\n");
extractedCount++;
Invoke(new Action(() =>
{
installProgress.Value = extractedCount;
statusLabel.Text = $"Skipped: ({extractedCount}/{totalFiles})";
}));
line = proc.StandardOutput.ReadLine();
if (line == null)
continue;
}
// Copia stream su file
using (srcStream)
using (FileStream dst = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None))
var match = regex.Match(line);
if (match.Success)
{
srcStream.CopyTo(dst);
}
extractedCount++;
int percent = int.Parse(match.Groups[1].Value);
if (percent != lastPercent)
{
lastPercent = percent;
Invoke(new Action(() =>
{
installProgress.Value = extractedCount;
statusLabel.Text = $"Extracted ({extractedCount}/{totalFiles})";
}));
}
catch (OperationCanceledException)
{
// rilanciamo per la chiamante
throw;
}
catch (Exception exFile)
{
File.AppendAllText(logPath, $"[ERROR] File: '{sourcePath}' -> {exFile.GetType().Name}: {exFile.Message}\r\n");
extractedCount++;
Invoke(new Action(() =>
{
installProgress.Value = extractedCount;
statusLabel.Text = $"Error at ({extractedCount}/{totalFiles})";
installProgress.Value = percent;
statusLabel.Text = $"Extracting... {percent}%";
}));
}
}
File.AppendAllText(logPath, $"Extraction finished: {DateTime.Now}\r\n");
}
}, token);
proc.WaitForExit();
if (proc.ExitCode != 0)
throw new Exception($"7-Zip exited with code {proc.ExitCode}");
}
/// <summary>
/// Tenta di aprire il file usando diverse varianti del percorso richiesto dal reader.
/// Restituisce lo Stream se riesce, altrimenti null.
/// </summary>
private Stream TryOpenFileWithVariants(object reader, string originalPath, string logPath)
{
// Normalizzazioni da provare
var candidates = new List<string>();
// percorso così com'è (probabilmente con '/')
candidates.Add(originalPath);
// versione senza slash iniziale
if (originalPath.StartsWith("/") || originalPath.StartsWith("\\"))
candidates.Add(originalPath.TrimStart('/', '\\'));
// con slash iniziale
if (!originalPath.StartsWith("/"))
candidates.Add("/" + originalPath.TrimStart('/', '\\'));
// replace slash/backslash
candidates.Add(originalPath.Replace('\\', '/'));
candidates.Add(originalPath.Replace('/', '\\'));
// varianti con normalizzazione Unicode (Form C)
try
{
string normalized = originalPath.Normalize(System.Text.NormalizationForm.FormC);
if (!candidates.Contains(normalized))
candidates.Add(normalized);
}
catch { /* ignore */ }
// proviamo le candidate una alla volta
foreach (string p in candidates.Distinct())
{
try
{
// alcuni reader richiedono stringa con '/', altri accettano entrambe;
// usiamo dynamic per chiamare OpenFile
var rdr = reader as dynamic;
Stream s = null;
// Proviamo prima con (string, FileMode.Open)
try
{
s = rdr.OpenFile(p, FileMode.Open);
}
catch
{
// se sbaglia, proviamo a chiamare senza FileMode (alcune versioni hanno overload diverso)
try
{
s = rdr.OpenFile(p);
}
catch
{
s = null;
}
}
if (s != null)
return s;
}
catch (Exception ex)
{
// loggare l'eccezione ma continuiamo con altre varianti
File.AppendAllText(logPath, $"[TRY FAILED] '{p}' => {ex.Message}\r\n");
}
}
// non abbiamo trovato nulla
return null;
}

View File

@@ -1,5 +1,4 @@
using DiscUtils.Udf;
using Microsoft.Win32;
using Microsoft.Win32;
using Newtonsoft.Json;
using Perfect11.TweaksInterface;
using System;

View File

@@ -28,6 +28,8 @@
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -59,18 +61,6 @@
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="DiscUtils.Core, Version=0.16.0.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
<HintPath>..\packages\DiscUtils.Core.0.16.13\lib\net45\DiscUtils.Core.dll</HintPath>
</Reference>
<Reference Include="DiscUtils.Iso9660, Version=0.16.0.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
<HintPath>..\packages\DiscUtils.Iso9660.0.16.13\lib\net45\DiscUtils.Iso9660.dll</HintPath>
</Reference>
<Reference Include="DiscUtils.Streams, Version=0.16.0.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
<HintPath>..\packages\DiscUtils.Streams.0.16.13\lib\net45\DiscUtils.Streams.dll</HintPath>
</Reference>
<Reference Include="DiscUtils.Udf, Version=0.16.0.0, Culture=neutral, PublicKeyToken=5fa5b410cc9c6289, processorArchitecture=MSIL">
<HintPath>..\packages\DiscUtils.Udf.0.16.13\lib\net45\DiscUtils.Udf.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -126,6 +116,12 @@
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<None Include="Tools\7z.sfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Tools\7zCon.sfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
@@ -146,7 +142,17 @@
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<None Include="Tools\7-zip.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Tools\7z.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Tools\7z.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Resources\unattend-OOBEAutomate.xml" />
<None Include="Resources\EI.CFG.txt" />
<Content Include="Resources\UWPSystemAppList.txt" />
<Content Include="Simpleicons-Team-Simple-Windows-11.ico" />
</ItemGroup>

View File

@@ -60,6 +60,18 @@ namespace Perfect11.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to [Channel]
///OEM
///[VL]
///0.
/// </summary>
internal static string EI_CFG {
get {
return ResourceManager.GetString("EI_CFG", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
///&lt;unattend xmlns=&quot;urn:schemas-microsoft-com:unattend&quot;&gt;

View File

@@ -118,6 +118,9 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="EI_CFG" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\EI.CFG.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
<data name="unattend_OOBEAutomate" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\unattend-OOBEAutomate.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>

View File

@@ -0,0 +1,4 @@
[Channel]
OEM
[VL]
0

BIN
Perfect11/Tools/7-zip.dll Normal file

Binary file not shown.

BIN
Perfect11/Tools/7z.dll Normal file

Binary file not shown.

BIN
Perfect11/Tools/7z.exe Normal file

Binary file not shown.

BIN
Perfect11/Tools/7z.sfx Normal file

Binary file not shown.

BIN
Perfect11/Tools/7zCon.sfx Normal file

Binary file not shown.

View File

@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DiscUtils.Core" version="0.16.13" targetFramework="net48" />
<package id="DiscUtils.Iso9660" version="0.16.13" targetFramework="net48" />
<package id="DiscUtils.Streams" version="0.16.13" targetFramework="net48" />
<package id="DiscUtils.Udf" version="0.16.13" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net48" />
<package id="ReaLTaiizor" version="3.8.1.3" targetFramework="net48" />
</packages>