v1.16 — Remove PowerShell edition, restore flat project structure

PowerShell version removed — will use OV code signing certificate instead.
Files moved back from src/ to project root.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hariel1985
2026-04-02 07:07:27 +02:00
szülő ae4d7f82bc
commit 486589f9bc
44 fájl változott, egészen pontosan 0 új sor hozzáadva és 1155 régi sor törölve

185
Services/LicenseManager.cs Normal file
Fájl megtekintése

@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace InstaSoftOfficeTool.Services
{
public class LicenseEntry
{
public string LicenseName { get; set; } = "";
public string Description { get; set; } = "";
public string Status { get; set; } = "";
public string ErrorDescription { get; set; } = "";
public string Last5 { get; set; } = "";
}
public class LicenseManager
{
public string OsppPath { get; private set; }
public bool FindOspp()
{
var searchPaths = new[]
{
// Click-to-Run (leggyakoribb — Office 365, 2019, 2021, 2024)
@"C:\Program Files\Microsoft Office\root\Office16\OSPP.VBS",
@"C:\Program Files (x86)\Microsoft Office\root\Office16\OSPP.VBS",
// Hagyományos MSI
@"C:\Program Files\Microsoft Office\Office16\ospp.vbs",
@"C:\Program Files (x86)\Microsoft Office\Office16\ospp.vbs",
@"C:\Program Files\Microsoft Office\Office15\ospp.vbs",
@"C:\Program Files (x86)\Microsoft Office\Office15\ospp.vbs",
@"C:\Program Files\Microsoft Office\Office14\ospp.vbs",
@"C:\Program Files (x86)\Microsoft Office\Office14\ospp.vbs",
};
foreach (var path in searchPaths)
{
if (File.Exists(path))
{
OsppPath = path;
return true;
}
}
try
{
var c2rPath = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
@"SOFTWARE\Microsoft\Office\ClickToRun\Configuration")
?.GetValue("InstallationPath") as string;
if (!string.IsNullOrEmpty(c2rPath))
{
// C2R: root\Office16 vagy sima Office16
var candidates = new[]
{
Path.Combine(c2rPath, "root", "Office16", "OSPP.VBS"),
Path.Combine(c2rPath, "Office16", "OSPP.VBS"),
};
foreach (var candidate in candidates)
{
if (File.Exists(candidate))
{
OsppPath = candidate;
return true;
}
}
}
}
catch { }
return false;
}
public async Task<string> GetStatusAsync()
{
if (string.IsNullOrEmpty(OsppPath))
return "Az ospp.vbs nem tal\u00e1lhat\u00f3.";
var runner = new ProcessRunner();
return await runner.RunAndCaptureAsync("cscript",
"//Nologo \"" + OsppPath + "\" /dstatus");
}
public List<string> ParseLicenseKeys(string dstatusOutput)
{
var keys = new List<string>();
var regex = new Regex(@"Last 5 characters of installed product key:\s*(\S+)",
RegexOptions.IgnoreCase);
foreach (Match match in regex.Matches(dstatusOutput))
{
keys.Add(match.Groups[1].Value);
}
return keys;
}
public List<LicenseEntry> ParseLicenseEntries(string dstatusOutput)
{
var entries = new List<LicenseEntry>();
// Split by "-------" separator blocks
var blocks = Regex.Split(dstatusOutput, @"-{10,}");
foreach (var block in blocks)
{
var trimmed = block.Trim();
if (string.IsNullOrEmpty(trimmed)) continue;
if (trimmed.StartsWith("---Processing") || trimmed.StartsWith("---Exiting")) continue;
var entry = new LicenseEntry();
var nameMatch = Regex.Match(trimmed, @"LICENSE NAME:\s*(.+)", RegexOptions.IgnoreCase);
if (nameMatch.Success) entry.LicenseName = nameMatch.Groups[1].Value.Trim();
var descMatch = Regex.Match(trimmed, @"LICENSE DESCRIPTION:\s*(.+)", RegexOptions.IgnoreCase);
if (descMatch.Success) entry.Description = descMatch.Groups[1].Value.Trim();
var statusMatch = Regex.Match(trimmed, @"LICENSE STATUS:\s*(.+)", RegexOptions.IgnoreCase);
if (statusMatch.Success) entry.Status = statusMatch.Groups[1].Value.Trim();
var errorMatch = Regex.Match(trimmed, @"ERROR DESCRIPTION:\s*(.+)", RegexOptions.IgnoreCase);
if (errorMatch.Success) entry.ErrorDescription = errorMatch.Groups[1].Value.Trim();
var keyMatch = Regex.Match(trimmed, @"Last 5 characters of installed product key:\s*(\S+)", RegexOptions.IgnoreCase);
if (keyMatch.Success) entry.Last5 = keyMatch.Groups[1].Value.Trim();
if (!string.IsNullOrEmpty(entry.Last5))
entries.Add(entry);
}
return entries;
}
public async Task<string> RemoveKeyAsync(string last5Chars)
{
if (string.IsNullOrEmpty(OsppPath))
return "Az ospp.vbs nem tal\u00e1lhat\u00f3.";
var runner = new ProcessRunner();
return await runner.RunAndCaptureAsync("cscript",
"//Nologo \"" + OsppPath + "\" /unpkey:" + last5Chars);
}
public async Task<string> RemoveAllKeysAsync()
{
var status = await GetStatusAsync();
var keys = ParseLicenseKeys(status);
if (keys.Count == 0)
return "Nem tal\u00e1lhat\u00f3 telep\u00edtett term\u00e9kkulcs.";
var results = new List<string>();
foreach (var key in keys)
{
var result = await RemoveKeyAsync(key);
results.Add(key + ": " + result.Trim());
}
return string.Join("\n", results);
}
public async Task<string> InstallKeyAsync(string productKey)
{
if (string.IsNullOrEmpty(OsppPath))
return "Az ospp.vbs nem tal\u00e1lhat\u00f3.";
var runner = new ProcessRunner();
return await runner.RunAndCaptureAsync("cscript",
"//Nologo \"" + OsppPath + "\" /inpkey:" + productKey);
}
public async Task<string> ActivateAsync()
{
if (string.IsNullOrEmpty(OsppPath))
return "Az ospp.vbs nem tal\u00e1lhat\u00f3.";
var runner = new ProcessRunner();
return await runner.RunAndCaptureAsync("cscript",
"//Nologo \"" + OsppPath + "\" /act");
}
}
}

113
Services/OdtDownloader.cs Normal file
Fájl megtekintése

@@ -0,0 +1,113 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace InstaSoftOfficeTool.Services
{
public class OdtDownloader
{
// Direct CDN link to ODT setup.exe (no self-extractor needed)
private const string OdtDownloadUrl = "https://officecdn.microsoft.com/pr/wsus/setup.exe";
public event Action<string> StatusChanged;
public string OdtFolder { get; private set; }
public string SetupExePath { get; private set; }
public OdtDownloader()
{
OdtFolder = Path.Combine(Path.GetTempPath(), "InstaSoftODT");
}
public async Task<bool> DownloadAndExtractAsync()
{
try
{
SetupExePath = Path.Combine(OdtFolder, "setup.exe");
// Check if valid setup.exe already exists
if (File.Exists(SetupExePath))
{
var existingSize = new FileInfo(SetupExePath).Length;
if (existingSize > 1000000) // valid setup.exe is ~7MB
{
StatusChanged?.Invoke("Az ODT setup.exe m\u00e1r el\u00e9rhet\u0151 (" +
(existingSize / 1024 / 1024) + " MB).");
return true;
}
// Delete corrupted file
StatusChanged?.Invoke("S\u00e9r\u00fclt setup.exe t\u00f6rl\u00e9se...");
File.Delete(SetupExePath);
}
Directory.CreateDirectory(OdtFolder);
// Download setup.exe directly from CDN
StatusChanged?.Invoke("Office Deployment Tool let\u00f6lt\u00e9se...");
StatusChanged?.Invoke("URL: " + OdtDownloadUrl);
using (var handler = new HttpClientHandler { AllowAutoRedirect = true })
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Add("User-Agent", "InstaSoftOfficeTool/1.0");
client.Timeout = TimeSpan.FromMinutes(5);
var response = await client.GetAsync(OdtDownloadUrl);
if (!response.IsSuccessStatusCode)
{
StatusChanged?.Invoke("Hiba: HTTP " + (int)response.StatusCode + " " + response.StatusCode);
return false;
}
var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
if (contentType.Contains("text/html"))
{
StatusChanged?.Invoke("Hiba: HTML oldal \u00e9rkezett exe helyett.");
StatusChanged?.Invoke("T\u00f6ltse le manu\u00e1lisan: https://www.microsoft.com/en-us/download/details.aspx?id=49117");
return false;
}
var bytes = await response.Content.ReadAsByteArrayAsync();
File.WriteAllBytes(SetupExePath, bytes);
StatusChanged?.Invoke("Let\u00f6ltve: " + (bytes.Length / 1024) + " KB");
}
if (!File.Exists(SetupExePath) || new FileInfo(SetupExePath).Length < 1000000)
{
StatusChanged?.Invoke("Hiba: A let\u00f6lt\u00f6tt f\u00e1jl s\u00e9r\u00fclt vagy t\u00fal kicsi.");
return false;
}
StatusChanged?.Invoke("ODT setup.exe k\u00e9sz.");
return true;
}
catch (Exception ex)
{
StatusChanged?.Invoke("Hiba: " + ex.GetType().Name + ": " + ex.Message);
return false;
}
}
public async Task<int> RunSetupAsync(string configXmlPath, Action<string> outputCallback)
{
var runner = new ProcessRunner();
runner.OutputReceived += line => outputCallback?.Invoke(line);
StatusChanged?.Invoke("Office telep\u00edt\u00e9s ind\u00edt\u00e1sa...");
return await runner.RunAsync(SetupExePath, "/configure \"" + configXmlPath + "\"");
}
public async Task<int> RunRemoveAsync(string configXmlPath, Action<string> outputCallback)
{
var runner = new ProcessRunner();
runner.OutputReceived += line => outputCallback?.Invoke(line);
StatusChanged?.Invoke("Office elt\u00e1vol\u00edt\u00e1s ind\u00edt\u00e1sa...");
return await runner.RunAsync(SetupExePath, "/configure \"" + configXmlPath + "\"");
}
}
}

Fájl megtekintése

@@ -0,0 +1,79 @@
using System.Xml.Linq;
using InstaSoftOfficeTool.Models;
namespace InstaSoftOfficeTool.Services
{
public static class OdtXmlGenerator
{
public static string Generate(InstallConfig config)
{
var product = new XElement("Product",
new XAttribute("ID", config.Edition.ProductId));
if (!string.IsNullOrWhiteSpace(config.ProductKey))
{
product.Add(new XAttribute("PIDKEY", config.ProductKey.Replace("-", "").Trim()));
}
product.Add(new XElement("Language", new XAttribute("ID", config.Language)));
foreach (var app in config.ExcludedApps)
{
product.Add(new XElement("ExcludeApp", new XAttribute("ID", app)));
}
var add = new XElement("Add",
new XAttribute("OfficeClientEdition", config.Architecture),
new XAttribute("Channel", config.Edition.Channel),
product);
var configuration = new XElement("Configuration",
add,
new XElement("Display",
new XAttribute("Level", "Full"),
new XAttribute("AcceptEULA", "TRUE")),
new XElement("Property",
new XAttribute("Name", "FORCEAPPSHUTDOWN"),
new XAttribute("Value", "TRUE")));
var doc = new XDocument(new XDeclaration("1.0", "utf-8", null), configuration);
return doc.Declaration + "\n" + doc.Root;
}
public static string GenerateRemoveAll()
{
var configuration = new XElement("Configuration",
new XElement("Remove", new XAttribute("All", "TRUE")),
new XElement("Display",
new XAttribute("Level", "Full"),
new XAttribute("AcceptEULA", "TRUE")),
new XElement("Property",
new XAttribute("Name", "FORCEAPPSHUTDOWN"),
new XAttribute("Value", "TRUE")));
var doc = new XDocument(new XDeclaration("1.0", "utf-8", null), configuration);
return doc.Declaration + "\n" + doc.Root;
}
public static string GenerateRemoveProducts(string[] productIds)
{
var remove = new XElement("Remove");
foreach (var id in productIds)
{
remove.Add(new XElement("Product", new XAttribute("ID", id)));
}
var configuration = new XElement("Configuration",
remove,
new XElement("Display",
new XAttribute("Level", "Full"),
new XAttribute("AcceptEULA", "TRUE")),
new XElement("Property",
new XAttribute("Name", "FORCEAPPSHUTDOWN"),
new XAttribute("Value", "TRUE")));
var doc = new XDocument(new XDeclaration("1.0", "utf-8", null), configuration);
return doc.Declaration + "\n" + doc.Root;
}
}
}

116
Services/OfficeDetector.cs Normal file
Fájl megtekintése

@@ -0,0 +1,116 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using InstaSoftOfficeTool.Models;
using Microsoft.Win32;
namespace InstaSoftOfficeTool.Services
{
public static class OfficeDetector
{
public static List<InstalledOffice> Detect()
{
var results = new List<InstalledOffice>();
// Check Click-to-Run
DetectClickToRun(results);
// Check MSI-based installs
DetectMsi(results);
return results;
}
private static void DetectClickToRun(List<InstalledOffice> results)
{
try
{
using (var key = Registry.LocalMachine.OpenSubKey(
@"SOFTWARE\Microsoft\Office\ClickToRun\Configuration"))
{
if (key == null) return;
var productIds = key.GetValue("ProductReleaseIds") as string;
var versionToReport = key.GetValue("VersionToReport") as string;
if (string.IsNullOrEmpty(productIds)) return;
// Split into individual products (e.g. "O365BusinessRetail,VisioProRetail")
var ids = productIds.Split(',');
foreach (var id in ids)
{
var trimmedId = id.Trim();
if (string.IsNullOrEmpty(trimmedId)) continue;
results.Add(new InstalledOffice
{
DisplayName = "Microsoft Office Click-to-Run (" + trimmedId + ")",
Version = versionToReport ?? "",
ProductCode = trimmedId,
IsClickToRun = true,
IsSelected = true
});
}
}
}
catch { }
}
private static void DetectMsi(List<InstalledOffice> results)
{
var uninstallPaths = new[]
{
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
};
foreach (var path in uninstallPaths)
{
try
{
using (var key = Registry.LocalMachine.OpenSubKey(path))
{
if (key == null) continue;
foreach (var subKeyName in key.GetSubKeyNames())
{
using (var subKey = key.OpenSubKey(subKeyName))
{
if (subKey == null) continue;
var displayName = subKey.GetValue("DisplayName") as string;
var publisher = subKey.GetValue("Publisher") as string;
if (displayName != null &&
publisher != null &&
publisher.Contains("Microsoft") &&
(displayName.Contains("Microsoft Office") ||
displayName.Contains("Microsoft 365")))
{
var version = subKey.GetValue("DisplayVersion") as string ?? "";
// Skip Click-to-Run updater entries
if (displayName.Contains("Click-to-Run") &&
!displayName.Contains("Microsoft Office"))
continue;
// Only accept {GUID} product codes for MSI uninstall
bool isGuid = Regex.IsMatch(subKeyName, @"^\{[0-9A-Fa-f\-]+\}$");
results.Add(new InstalledOffice
{
DisplayName = displayName,
Version = version,
ProductCode = isGuid ? subKeyName : null,
IsClickToRun = false,
IsSelected = true
});
}
}
}
}
}
catch { }
}
}
}
}

63
Services/ProcessRunner.cs Normal file
Fájl megtekintése

@@ -0,0 +1,63 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
namespace InstaSoftOfficeTool.Services
{
public class ProcessRunner
{
public event Action<string> OutputReceived;
public async Task<int> RunAsync(string fileName, string arguments, bool redirectOutput = true)
{
var psi = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = redirectOutput,
RedirectStandardError = redirectOutput,
StandardOutputEncoding = redirectOutput ? Encoding.UTF8 : null,
StandardErrorEncoding = redirectOutput ? Encoding.UTF8 : null
};
using (var process = new Process { StartInfo = psi })
{
if (redirectOutput)
{
process.OutputDataReceived += (s, e) =>
{
if (e.Data != null)
OutputReceived?.Invoke(e.Data);
};
process.ErrorDataReceived += (s, e) =>
{
if (e.Data != null)
OutputReceived?.Invoke("[HIBA] " + e.Data);
};
}
process.Start();
if (redirectOutput)
{
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
await Task.Run(() => process.WaitForExit());
return process.ExitCode;
}
}
public async Task<string> RunAndCaptureAsync(string fileName, string arguments)
{
var sb = new StringBuilder();
OutputReceived += line => sb.AppendLine(line);
await RunAsync(fileName, arguments);
return sb.ToString();
}
}
}