From 4937ac4b1ced203c231c9c35caee968f182ba442 Mon Sep 17 00:00:00 2001 From: hariel1985 Date: Tue, 31 Mar 2026 05:40:50 +0200 Subject: [PATCH] =?UTF-8?q?Initial=20release=20v1.01=20=E2=80=94=20InstaSo?= =?UTF-8?q?ft=20Office=20Tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Office deployment wizard for InstaSoft customers: - Install Office 2019/2021/2024 (Standard, Professional Plus, Home & Business) - Auto-download ODT from Microsoft, generate config XML, run setup - Remove existing Office installations (C2R + MSI) - License troubleshooting via ospp.vbs (dstatus, unpkey) - Fluent Design UI (WPF .NET Framework 4.8, Win7+ compatible) - Hungarian interface, multi-language Office installation - Product key input with auto-activation support Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 7 ++ App.xaml | 14 +++ App.xaml.cs | 8 ++ InstaSoftOfficeTool.csproj | 26 +++++ MainWindow.xaml | 73 ++++++++++++ MainWindow.xaml.cs | 205 +++++++++++++++++++++++++++++++++ Models/InstallConfig.cs | 81 +++++++++++++ Models/InstalledOffice.cs | 11 ++ Models/OfficeEdition.cs | 107 +++++++++++++++++ Models/OfficeVersion.cs | 9 ++ Pages/ConfigPage.xaml | 33 ++++++ Pages/ConfigPage.xaml.cs | 86 ++++++++++++++ Pages/EditionPage.xaml | 20 ++++ Pages/EditionPage.xaml.cs | 80 +++++++++++++ Pages/ProductKeyPage.xaml | 62 ++++++++++ Pages/ProductKeyPage.xaml.cs | 112 ++++++++++++++++++ Pages/ProgressPage.xaml | 56 +++++++++ Pages/ProgressPage.xaml.cs | 140 ++++++++++++++++++++++ Pages/RemovePage.xaml | 52 +++++++++ Pages/RemovePage.xaml.cs | 131 +++++++++++++++++++++ Pages/SummaryPage.xaml | 63 ++++++++++ Pages/SummaryPage.xaml.cs | 63 ++++++++++ Pages/TroubleshootPage.xaml | 45 ++++++++ Pages/TroubleshootPage.xaml.cs | 106 +++++++++++++++++ Pages/VersionPage.xaml | 47 ++++++++ Pages/VersionPage.xaml.cs | 34 ++++++ Pages/WelcomePage.xaml | 59 ++++++++++ Pages/WelcomePage.xaml.cs | 31 +++++ Resources/app.ico | Bin 0 -> 2473 bytes Services/LicenseManager.cs | 119 +++++++++++++++++++ Services/OdtDownloader.cs | 96 +++++++++++++++ Services/OdtXmlGenerator.cs | 55 +++++++++ Services/OfficeDetector.cs | 104 +++++++++++++++++ Services/ProcessRunner.cs | 63 ++++++++++ Styles/ButtonStyles.xaml | 119 +++++++++++++++++++ Styles/ControlStyles.xaml | 104 +++++++++++++++++ Styles/FluentTheme.xaml | 49 ++++++++ app.manifest | 26 +++++ 38 files changed, 2496 insertions(+) create mode 100644 .gitignore create mode 100644 App.xaml create mode 100644 App.xaml.cs create mode 100644 InstaSoftOfficeTool.csproj create mode 100644 MainWindow.xaml create mode 100644 MainWindow.xaml.cs create mode 100644 Models/InstallConfig.cs create mode 100644 Models/InstalledOffice.cs create mode 100644 Models/OfficeEdition.cs create mode 100644 Models/OfficeVersion.cs create mode 100644 Pages/ConfigPage.xaml create mode 100644 Pages/ConfigPage.xaml.cs create mode 100644 Pages/EditionPage.xaml create mode 100644 Pages/EditionPage.xaml.cs create mode 100644 Pages/ProductKeyPage.xaml create mode 100644 Pages/ProductKeyPage.xaml.cs create mode 100644 Pages/ProgressPage.xaml create mode 100644 Pages/ProgressPage.xaml.cs create mode 100644 Pages/RemovePage.xaml create mode 100644 Pages/RemovePage.xaml.cs create mode 100644 Pages/SummaryPage.xaml create mode 100644 Pages/SummaryPage.xaml.cs create mode 100644 Pages/TroubleshootPage.xaml create mode 100644 Pages/TroubleshootPage.xaml.cs create mode 100644 Pages/VersionPage.xaml create mode 100644 Pages/VersionPage.xaml.cs create mode 100644 Pages/WelcomePage.xaml create mode 100644 Pages/WelcomePage.xaml.cs create mode 100644 Resources/app.ico create mode 100644 Services/LicenseManager.cs create mode 100644 Services/OdtDownloader.cs create mode 100644 Services/OdtXmlGenerator.cs create mode 100644 Services/OfficeDetector.cs create mode 100644 Services/ProcessRunner.cs create mode 100644 Styles/ButtonStyles.xaml create mode 100644 Styles/ControlStyles.xaml create mode 100644 Styles/FluentTheme.xaml create mode 100644 app.manifest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81de725 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +bin/ +obj/ +*.user +*.suo +.vs/ +*.DotSettings.user +packages/ diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..44d89bc --- /dev/null +++ b/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..86cde79 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,8 @@ +using System.Windows; + +namespace InstaSoftOfficeTool +{ + public partial class App : Application + { + } +} diff --git a/InstaSoftOfficeTool.csproj b/InstaSoftOfficeTool.csproj new file mode 100644 index 0000000..9d07aaf --- /dev/null +++ b/InstaSoftOfficeTool.csproj @@ -0,0 +1,26 @@ + + + WinExe + net48 + true + InstaSoftOfficeTool + InstaSoftOfficeTool + Resources\app.ico + InstaSoft Zrt. + InstaSoft Office Tool + Copyright (c) InstaSoft Zrt. 2026 + 1.0.0 + 1.0.0.0 + 1.0.0.0 + app.manifest + latest + + + + + + + + + + diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..d7ac07c --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pages/WelcomePage.xaml.cs b/Pages/WelcomePage.xaml.cs new file mode 100644 index 0000000..2bee20d --- /dev/null +++ b/Pages/WelcomePage.xaml.cs @@ -0,0 +1,31 @@ +using System.Windows; +using System.Windows.Controls; + +namespace InstaSoftOfficeTool.Pages +{ + public partial class WelcomePage : Page + { + private readonly MainWindow _main; + + public WelcomePage(MainWindow main) + { + InitializeComponent(); + _main = main; + } + + private void InstallClick(object sender, RoutedEventArgs e) + { + _main.StartInstallFlow(); + } + + private void RemoveClick(object sender, RoutedEventArgs e) + { + _main.StartRemoveFlow(); + } + + private void LicenseClick(object sender, RoutedEventArgs e) + { + _main.StartLicenseFlow(); + } + } +} diff --git a/Resources/app.ico b/Resources/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..d5d425dfe413ca42c7d4a27c3230260ff2894a7b GIT binary patch literal 2473 zcmZQzU}Run5D;Jh(h3Z0j0_BJ3=9kk3J|^uknaP;1_ltm2asLB1XKV7AoU+v85njj zGca@p___0PNpS&%c|AQ`f`By8L&U>c zv7h@-BG3#5&H|6fVg?4j!ywFfJby(BP;j=Vi(`m|e{#ip{qz4VBW}t`{}WoZD1Lu) z=-xW>*|uSO<~z?}Tom!u-t3R2M``@yxNVia)i?enAO2b$@yMNbK^x3=T%73GsqFsZMTS!0 zGl@Q4@xmYv3&x-p<*jUK^CmJz-fuZveX{=VCvU4OpZ`7FRk3`R#g!YsPTO5t%Mo;qRmc6dirG!0 zMAhDh;oF8;>3kbD?)E*l(Ti!@!b$tp6&N6z9#|hSx}NaaxV&X~Pg~a2gyx_)^#hZ> zGtOoWmayP%_y_bhzAyr&Ed~Q{7=<(aY$q#>?t;Q7!})FhM&4!v5tmqjLkb%Ne4Zpe zQj~cTXv5qn=dM{-cxh_9l2iGmZ2$MI3;v$`R_XKGM%4d}^*(vcb+sQ^-a2y}H@cQQ zbC&uy+3V|$ZOxyzU@w!!x3vsRx3n9Q?{g^-;Qp0m+OqokQvX+%!fZCaH~#+e%=t&0 zxBfH5Mb|D_TbXn}T`udi^JkW~q6aqSvkF{@W0d(F#=uB``(9u{>=u35D=K+vbLNWU z4@Y3u1m;O_IK~vMNFyg4S=&J2SP}R3_QqV<6p7YE<-0R-96TFimM`(~oX_(>P~zYx zCLhL=OePx__Z;H#Yp@buy4Px&hD_(_-!d;}KKT8$?Elf$nb^SJ``^F6ULF6hru1v$ z=g%*%Uj6#gR{r+czfb+Qzt^`fXnQw*ujSt7li!|p{`LCn)rp%+imT2aFZ=wjp!WB_ zPbVy`{uEZ7SD(!=$CS}SfWd5(7!3og>EK5fAeE-wE5=K1C7Sy+;1Y)p(yU2Vn3v$xx)&zXOEJOBOp_daiwt*`%g_v61$s?VQ) zzPsIi-o4K!%PKy9JiOUlzJJdA!} 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 ParseLicenseKeys(string dstatusOutput) + { + var keys = new List(); + 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 async Task 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 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(); + foreach (var key in keys) + { + var result = await RemoveKeyAsync(key); + results.Add(key + ": " + result.Trim()); + } + + return string.Join("\n", results); + } + } +} diff --git a/Services/OdtDownloader.cs b/Services/OdtDownloader.cs new file mode 100644 index 0000000..816337f --- /dev/null +++ b/Services/OdtDownloader.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; + +namespace InstaSoftOfficeTool.Services +{ + public class OdtDownloader + { + private const string OdtDownloadUrl = "https://go.microsoft.com/fwlink/p/?LinkID=626065"; + + public event Action StatusChanged; + public event Action ProgressChanged; + + public string OdtFolder { get; private set; } + public string SetupExePath { get; private set; } + + public OdtDownloader() + { + OdtFolder = Path.Combine(Path.GetTempPath(), "InstaSoftODT"); + } + + public async Task DownloadAndExtractAsync() + { + try + { + Directory.CreateDirectory(OdtFolder); + + var odtExePath = Path.Combine(OdtFolder, "officedeploymenttool.exe"); + SetupExePath = Path.Combine(OdtFolder, "setup.exe"); + + if (File.Exists(SetupExePath)) + { + StatusChanged?.Invoke("Az ODT setup.exe m\u00e1r el\u00e9rhet\u0151, let\u00f6lt\u00e9s kihagyva."); + return true; + } + + StatusChanged?.Invoke("Office Deployment Tool let\u00f6lt\u00e9se..."); + + using (var client = new WebClient()) + { + client.DownloadProgressChanged += (s, e) => + { + ProgressChanged?.Invoke(e.ProgressPercentage); + }; + + await client.DownloadFileTaskAsync(new Uri(OdtDownloadUrl), odtExePath); + } + + StatusChanged?.Invoke("ODT kicsomagol\u00e1sa..."); + + var runner = new ProcessRunner(); + var exitCode = await runner.RunAsync(odtExePath, + "/extract:\"" + OdtFolder + "\" /quiet"); + + if (exitCode != 0) + { + StatusChanged?.Invoke("Hiba: Az ODT kicsomagol\u00e1sa sikertelen (k\u00f3d: " + exitCode + ")"); + return false; + } + + if (!File.Exists(SetupExePath)) + { + StatusChanged?.Invoke("Hiba: A setup.exe nem tal\u00e1lhat\u00f3 a kicsomagol\u00e1s ut\u00e1n."); + return false; + } + + StatusChanged?.Invoke("ODT sikeresen let\u00f6ltve \u00e9s kicsomagolva."); + return true; + } + catch (Exception ex) + { + StatusChanged?.Invoke("Hiba a let\u00f6lt\u00e9s sor\u00e1n: " + ex.Message); + return false; + } + } + + public async Task RunSetupAsync(string configXmlPath, Action 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 RunRemoveAsync(string configXmlPath, Action 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 + "\""); + } + } +} diff --git a/Services/OdtXmlGenerator.cs b/Services/OdtXmlGenerator.cs new file mode 100644 index 0000000..5ea332c --- /dev/null +++ b/Services/OdtXmlGenerator.cs @@ -0,0 +1,55 @@ +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"))); + + var doc = new XDocument(new XDeclaration("1.0", "utf-8", null), configuration); + return doc.Declaration + "\n" + doc.Root; + } + } +} diff --git a/Services/OfficeDetector.cs b/Services/OfficeDetector.cs new file mode 100644 index 0000000..8694fe0 --- /dev/null +++ b/Services/OfficeDetector.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using InstaSoftOfficeTool.Models; +using Microsoft.Win32; + +namespace InstaSoftOfficeTool.Services +{ + public static class OfficeDetector + { + public static List Detect() + { + var results = new List(); + + // Check Click-to-Run + DetectClickToRun(results); + + // Check MSI-based installs + DetectMsi(results); + + return results; + } + + private static void DetectClickToRun(List 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)) + { + results.Add(new InstalledOffice + { + DisplayName = "Microsoft Office Click-to-Run (" + productIds + ")", + Version = versionToReport ?? "", + IsClickToRun = true, + IsSelected = true + }); + } + } + } + catch { } + } + + private static void DetectMsi(List 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; + + results.Add(new InstalledOffice + { + DisplayName = displayName, + Version = version, + ProductCode = subKeyName, + IsClickToRun = false, + IsSelected = true + }); + } + } + } + } + } + catch { } + } + } + } +} diff --git a/Services/ProcessRunner.cs b/Services/ProcessRunner.cs new file mode 100644 index 0000000..94f4be2 --- /dev/null +++ b/Services/ProcessRunner.cs @@ -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 OutputReceived; + + public async Task 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 RunAndCaptureAsync(string fileName, string arguments) + { + var sb = new StringBuilder(); + OutputReceived += line => sb.AppendLine(line); + await RunAsync(fileName, arguments); + return sb.ToString(); + } + } +} diff --git a/Styles/ButtonStyles.xaml b/Styles/ButtonStyles.xaml new file mode 100644 index 0000000..6f10a1d --- /dev/null +++ b/Styles/ButtonStyles.xaml @@ -0,0 +1,119 @@ + + + + + + + + + + + + diff --git a/Styles/ControlStyles.xaml b/Styles/ControlStyles.xaml new file mode 100644 index 0000000..34b58d9 --- /dev/null +++ b/Styles/ControlStyles.xaml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Styles/FluentTheme.xaml b/Styles/FluentTheme.xaml new file mode 100644 index 0000000..8735882 --- /dev/null +++ b/Styles/FluentTheme.xaml @@ -0,0 +1,49 @@ + + + + #0078D4 + #106EBE + #005A9E + #F3F3F3 + #FFFFFF + #F8F8F8 + #E0E0E0 + #1A1A1A + #666666 + #FFFFFF + #107C10 + #D13438 + #CA5010 + #FAFAFA + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app.manifest b/app.manifest new file mode 100644 index 0000000..075cff8 --- /dev/null +++ b/app.manifest @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + true/pm + PerMonitorV2 + + +