diff --git a/.gitignore b/.gitignore
index 81de725..cfa9a93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
-bin/
-obj/
+**/bin/
+**/obj/
*.user
*.suo
.vs/
diff --git a/powershell/InstaSoftOfficeTool.ps1 b/powershell/InstaSoftOfficeTool.ps1
new file mode 100644
index 0000000..a40dc42
--- /dev/null
+++ b/powershell/InstaSoftOfficeTool.ps1
@@ -0,0 +1,1155 @@
+#Requires -Version 5.1
+# InstaSoft Office Tool v1.14 — PowerShell Edition
+# Copyright (c) InstaSoft Informatikai Zrt. 2026
+# Office deployment wizard: install, remove, license management
+
+# --- Request admin elevation ---
+if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ Start-Process powershell.exe -ArgumentList "-ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
+ exit
+}
+
+Add-Type -AssemblyName PresentationFramework
+Add-Type -AssemblyName PresentationCore
+Add-Type -AssemblyName WindowsBase
+Add-Type -AssemblyName System.Net.Http
+
+# ============================================================
+# DATA
+# ============================================================
+$script:Config = @{
+ Version = 'Office2024'
+ Edition = $null
+ Architecture = '64'
+ Language = 'hu-hu'
+ ProductKey = ''
+ ExcludedApps = @()
+}
+
+$script:Editions = @{
+ Office2024 = @(
+ @{ DisplayName='Standard'; Description='Alapveto irodai alkalmazasok: Word, Excel, PowerPoint, Outlook, OneNote'; ProductId='Standard2024Volume'; Channel='PerpetualVL2024'; IsVolume=$true },
+ @{ DisplayName='Professional Plus'; Description='Teljes csomag: Word, Excel, PowerPoint, Outlook, Access, Publisher, OneNote'; ProductId='ProPlus2024Volume'; Channel='PerpetualVL2024'; IsVolume=$true },
+ @{ DisplayName="Otthoni `u{00e9}s kisv`u{00e1}llalati verzi`u{00f3}"; Description="Word, Excel, PowerPoint, Outlook, OneNote `u{2014} v`u{00e1}llalkoz`u{00e1}sokban is haszn`u{00e1}lhat`u{00f3}"; ProductId='HomeBusiness2024Retail'; Channel='Current'; IsVolume=$false }
+ )
+ Office2021 = @(
+ @{ DisplayName='Standard'; Description='Alapveto irodai alkalmazasok: Word, Excel, PowerPoint, Outlook, OneNote'; ProductId='Standard2021Volume'; Channel='PerpetualVL2021'; IsVolume=$true },
+ @{ DisplayName='Professional Plus'; Description='Teljes csomag: Word, Excel, PowerPoint, Outlook, Access, Publisher, OneNote'; ProductId='ProPlus2021Volume'; Channel='PerpetualVL2021'; IsVolume=$true },
+ @{ DisplayName="Otthoni `u{00e9}s kisv`u{00e1}llalati verzi`u{00f3}"; Description="Word, Excel, PowerPoint, Outlook, OneNote `u{2014} v`u{00e1}llalkoz`u{00e1}sokban is haszn`u{00e1}lhat`u{00f3}"; ProductId='HomeBusiness2021Retail'; Channel='Current'; IsVolume=$false }
+ )
+ Office2019 = @(
+ @{ DisplayName='Standard'; Description='Alapveto irodai alkalmazasok: Word, Excel, PowerPoint, Outlook, OneNote'; ProductId='Standard2019Volume'; Channel='PerpetualVL2019'; IsVolume=$true },
+ @{ DisplayName='Professional Plus'; Description='Teljes csomag: Word, Excel, PowerPoint, Outlook, Access, Publisher, OneNote, Skype for Business'; ProductId='ProPlus2019Volume'; Channel='PerpetualVL2019'; IsVolume=$true },
+ @{ DisplayName="Otthoni `u{00e9}s kisv`u{00e1}llalati verzi`u{00f3}"; Description="Word, Excel, PowerPoint, Outlook, OneNote `u{2014} v`u{00e1}llalkoz`u{00e1}sokban is haszn`u{00e1}lhat`u{00f3}"; ProductId='HomeBusiness2019Retail'; Channel='Current'; IsVolume=$false }
+ )
+}
+
+$script:Languages = @(
+ @('hu-hu','Magyar'), @('en-us','English (US)'), @('de-de','Deutsch'),
+ @('fr-fr','Fran\u00e7ais'), @('it-it','Italiano'), @('es-es','Espa\u00f1ol'),
+ @('pt-pt','Portugu\u00eas'), @('nl-nl','Nederlands'), @('pl-pl','Polski'),
+ @('cs-cz','\u010ce\u0161tina'), @('sk-sk','Sloven\u010dina'), @('ro-ro','Rom\u00e2n\u0103'),
+ @('hr-hr','Hrvatski'), @('sl-si','Sloven\u0161\u010dina'), @('sr-latn-rs','Srpski'),
+ @('bg-bg','Bulgarian'), @('uk-ua','Ukrainian'), @('ru-ru','Russian'),
+ @('tr-tr','T\u00fcrk\u00e7e'), @('ja-jp','Japanese'), @('zh-cn','Chinese'), @('ko-kr','Korean')
+)
+
+$script:ExcludableApps = @(
+ @{Id='Word'; Name='Word'; Default=$true; Min='all'},
+ @{Id='Excel'; Name='Excel'; Default=$true; Min='all'},
+ @{Id='PowerPoint'; Name='PowerPoint'; Default=$true; Min='all'},
+ @{Id='Outlook'; Name='Outlook'; Default=$true; Min='all'},
+ @{Id='OneNote'; Name='OneNote'; Default=$true; Min='all'},
+ @{Id='Access'; Name='Access'; Default=$true; Min='proplus'},
+ @{Id='Publisher'; Name='Publisher'; Default=$true; Min='standard+'},
+ @{Id='Teams'; Name='Teams'; Default=$false; Min='all'},
+ @{Id='Lync'; Name='Skype for Business'; Default=$false; Min='all'}
+)
+
+# ============================================================
+# XAML — Full UI in one window with panels
+# ============================================================
+[xml]$xaml = @'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+'@
+
+# ============================================================
+# CREATE WINDOW
+# ============================================================
+$reader = New-Object System.Xml.XmlNodeReader $xaml
+$window = [Windows.Markup.XamlReader]::Load($reader)
+
+# Find all named elements
+$names = @('PanelWelcome','PanelVersion','PanelEdition','PanelConfig','PanelKey','PanelSummary',
+ 'PanelProgress','PanelRemove','PanelLicense',
+ 'BtnInstall','BtnRemoveWelcome','BtnLicenseWelcome','BtnBack','BtnNext','StepDots',
+ 'Rb2024','Rb2021','Rb2019','Rb64','Rb32','CbLanguage','AppChecks',
+ 'EditionSubtitle','EditionCards',
+ 'K1','K2','K3','K4','K5','CbSkipKey',
+ 'SummaryText','XmlPreview',
+ 'ProgressTitle','ProgressStatus','ProgressBar','ProgressLog','DonePanel','DoneText',
+ 'ActivateCard','BtnActivatePost','LaunchCard','LaunchBtns',
+ 'RemoveList','BtnDoRemove',
+ 'LicenseCards','BtnLicRefresh',
+ 'OverlayConfirm','DlgTitle','DlgMsg','DlgOk','DlgCancel')
+
+$el = @{}
+foreach ($n in $names) {
+ $el[$n] = $window.FindName($n)
+}
+
+# ============================================================
+# SERVICES (PowerShell functions)
+# ============================================================
+function Show-Panel($name) {
+ $panels = @('PanelWelcome','PanelVersion','PanelEdition','PanelConfig','PanelKey','PanelSummary','PanelProgress','PanelRemove','PanelLicense')
+ foreach ($p in $panels) {
+ $el[$p].Visibility = if ($p -eq $name) { 'Visible' } else { 'Collapsed' }
+ }
+}
+
+$script:CurrentPanel = 'PanelWelcome'
+$script:InstallPanels = @('PanelVersion','PanelEdition','PanelConfig','PanelKey','PanelSummary','PanelProgress')
+$script:InstallIndex = 0
+
+function Update-StepDots {
+ $el['StepDots'].Children.Clear()
+ $total = $script:InstallPanels.Count - 1 # exclude progress
+ for ($i = 0; $i -lt $total; $i++) {
+ $dot = New-Object Windows.Shapes.Ellipse
+ $dot.Margin = [Windows.Thickness]::new(4,0,4,0)
+ if ($i -le $script:InstallIndex) {
+ $dot.Width = 10; $dot.Height = 10
+ $dot.Fill = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(0,120,212))
+ } else {
+ $dot.Width = 8; $dot.Height = 8
+ $dot.Fill = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(224,224,224))
+ }
+ $el['StepDots'].Children.Add($dot) | Out-Null
+ }
+}
+
+function Navigate-Install($index) {
+ $script:InstallIndex = $index
+ $panel = $script:InstallPanels[$index]
+ Show-Panel $panel
+ $script:CurrentPanel = $panel
+
+ if ($panel -eq 'PanelProgress') {
+ $el['BtnBack'].Visibility = 'Collapsed'
+ $el['BtnNext'].Visibility = 'Collapsed'
+ $el['StepDots'].Children.Clear()
+ } else {
+ $el['BtnBack'].Visibility = 'Visible'
+ $el['BtnNext'].Visibility = 'Visible'
+ $el['BtnNext'].Content = "Tov`u{00e1}bb `u{2192}"
+ Update-StepDots
+ }
+}
+
+function Get-SelectedVersion {
+ if ($el['Rb2024'].IsChecked) { return 'Office2024' }
+ if ($el['Rb2021'].IsChecked) { return 'Office2021' }
+ if ($el['Rb2019'].IsChecked) { return 'Office2019' }
+ return 'Office2024'
+}
+
+function Get-VersionDisplayName($v) {
+ switch ($v) {
+ 'Office2024' { 'Office 2024' }
+ 'Office2021' { 'Office 2021' }
+ 'Office2019' { 'Office 2019' }
+ }
+}
+
+function Build-EditionCards {
+ $el['EditionCards'].Children.Clear()
+ $ver = Get-SelectedVersion
+ $script:Config.Version = $ver
+ $el['EditionSubtitle'].Text = "$(Get-VersionDisplayName $ver) `u{2014} melyik kiad`u{00e1}st szeretn`u{00e9}?"
+ $eds = $script:Editions[$ver]
+ for ($i = 0; $i -lt $eds.Count; $i++) {
+ $ed = $eds[$i]
+ $rb = New-Object Windows.Controls.RadioButton
+ $rb.GroupName = 'Edition'
+ $rb.Style = $window.FindResource('CardRadio')
+ $rb.Margin = [Windows.Thickness]::new(0,0,0,10)
+ $rb.IsChecked = ($i -eq 0)
+ $rb.Tag = $i
+ $sp = New-Object Windows.Controls.StackPanel
+ $sp.Margin = [Windows.Thickness]::new(8,2,8,2)
+ $t1 = New-Object Windows.Controls.TextBlock
+ $t1.Text = $ed.DisplayName; $t1.FontSize = 18; $t1.FontWeight = 'SemiBold'
+ $t2 = New-Object Windows.Controls.TextBlock
+ $t2.Text = $ed.Description; $t2.FontSize = 12
+ $t2.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(102,102,102))
+ $sp.Children.Add($t1) | Out-Null
+ $sp.Children.Add($t2) | Out-Null
+ $rb.Content = $sp
+ $el['EditionCards'].Children.Add($rb) | Out-Null
+ }
+}
+
+function Get-SelectedEdition {
+ $ver = $script:Config.Version
+ $eds = $script:Editions[$ver]
+ foreach ($child in $el['EditionCards'].Children) {
+ if ($child -is [Windows.Controls.RadioButton] -and $child.IsChecked) {
+ return $eds[$child.Tag]
+ }
+ }
+ return $eds[0]
+}
+
+function Build-AppChecks {
+ $el['AppChecks'].Children.Clear()
+ $ed = $script:Config.Edition
+ $isProPlus = $ed -and $ed.ProductId -match 'ProPlus'
+ $isStandard = $ed -and $ed.ProductId -match 'Standard'
+ foreach ($app in $script:ExcludableApps) {
+ $unavailable = $false
+ if ($app.Min -eq 'proplus' -and -not $isProPlus) { $unavailable = $true }
+ elseif ($app.Min -eq 'standard+' -and -not $isProPlus -and -not $isStandard) { $unavailable = $true }
+ $cb = New-Object Windows.Controls.CheckBox
+ $cb.Content = $app.Name
+ $cb.IsChecked = if ($unavailable) { $false } else { $app.Default }
+ $cb.IsEnabled = -not $unavailable
+ $cb.Style = $window.FindResource('FluentCB')
+ $cb.Margin = [Windows.Thickness]::new(0,4,24,4)
+ $cb.MinWidth = 160
+ $cb.Tag = $app.Id
+ $el['AppChecks'].Children.Add($cb) | Out-Null
+ }
+}
+
+function Build-LanguageCombo {
+ $el['CbLanguage'].Items.Clear()
+ foreach ($lang in $script:Languages) {
+ $item = New-Object Windows.Controls.ComboBoxItem
+ $item.Content = "$($lang[1]) ($($lang[0]))"
+ $item.Tag = $lang[0]
+ $el['CbLanguage'].Items.Add($item) | Out-Null
+ }
+ $el['CbLanguage'].SelectedIndex = 0
+}
+
+function Get-ProductKey {
+ $parts = @($el['K1'].Text, $el['K2'].Text, $el['K3'].Text, $el['K4'].Text, $el['K5'].Text)
+ $all = ($parts | Where-Object { $_.Length -eq 5 })
+ if ($all.Count -eq 5) { return ($parts -join '-').ToUpper() }
+ return ''
+}
+
+function Generate-OdtXml {
+ $c = $script:Config
+ $xml = "`n`n"
+ $xml += " `n"
+ $xml += " `n"
+ $xml += " `n"
+ foreach ($app in $c.ExcludedApps) { $xml += " `n" }
+ $xml += " `n `n"
+ $xml += " `n"
+ $xml += " `n"
+ $xml += ""
+ return $xml
+}
+
+function Build-Summary {
+ $c = $script:Config
+ $keyText = if ($c.ProductKey) { $c.ProductKey } else { 'Nincs megadva' }
+ $exText = if ($c.ExcludedApps.Count -gt 0) { $c.ExcludedApps -join ', ' } else { "Nincs (minden alkalmazas telepul)" }
+ $langName = ($script:Languages | Where-Object { $_[0] -eq $c.Language } | Select-Object -First 1)[1]
+ $el['SummaryText'].Text = "Verzio: $(Get-VersionDisplayName $c.Version)`nKiadas: $($c.Edition.DisplayName)`nArchitektura: $($c.Architecture)-bit`nNyelv: $langName ($($c.Language))`nTermekkulcs: $keyText`nKizart alkalmazasok: $exText"
+ $el['XmlPreview'].Text = Generate-OdtXml
+}
+
+function Append-Log($text) {
+ $ts = Get-Date -Format 'HH:mm:ss'
+ $el['ProgressLog'].Text += "$ts $text`n"
+ $el['ProgressLog'].ScrollToEnd()
+ [Windows.Threading.Dispatcher]::CurrentDispatcher.Invoke([Action]{}, 'Background')
+}
+
+function Download-ODT {
+ $odtFolder = Join-Path $env:TEMP 'InstaSoftODT'
+ $setupPath = Join-Path $odtFolder 'setup.exe'
+ if (Test-Path $setupPath) {
+ $size = [math]::Round((Get-Item $setupPath).Length / 1MB, 1)
+ if ((Get-Item $setupPath).Length -gt 1000000) {
+ Append-Log "Az ODT setup.exe mar elerheto ($size MB)."
+ return $setupPath
+ }
+ }
+ New-Item -ItemType Directory -Path $odtFolder -Force | Out-Null
+ Append-Log "Office Deployment Tool letoltese..."
+ try {
+ $url = 'https://officecdn.microsoft.com/pr/wsus/setup.exe'
+ $client = New-Object System.Net.Http.HttpClient
+ $client.Timeout = [TimeSpan]::FromMinutes(5)
+ $response = $client.GetAsync($url).Result
+ if (-not $response.IsSuccessStatusCode) {
+ Append-Log "Hiba: HTTP $($response.StatusCode)"
+ return $null
+ }
+ $bytes = $response.Content.ReadAsByteArrayAsync().Result
+ [System.IO.File]::WriteAllBytes($setupPath, $bytes)
+ $client.Dispose()
+ Append-Log "Letoltve: $([math]::Round($bytes.Length / 1024)) KB"
+ return $setupPath
+ } catch {
+ Append-Log "Hiba: $($_.Exception.Message)"
+ return $null
+ }
+}
+
+function Run-Setup($setupPath, $xmlPath) {
+ Append-Log "setup.exe /configure `"$xmlPath`""
+ Append-Log "Ez akár 30-40 percet is igénybe vehet..."
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
+ $psi.FileName = $setupPath
+ $psi.Arguments = "/configure `"$xmlPath`""
+ $psi.UseShellExecute = $false
+ $psi.CreateNoWindow = $true
+ $p = [System.Diagnostics.Process]::Start($psi)
+ $p.WaitForExit()
+ return $p.ExitCode
+}
+
+function Start-Installation {
+ Show-Panel 'PanelProgress'
+ $el['BtnBack'].Visibility = 'Collapsed'
+ $el['BtnNext'].Visibility = 'Collapsed'
+ $el['StepDots'].Children.Clear()
+ $el['ProgressLog'].Text = ''
+ $el['DonePanel'].Visibility = 'Collapsed'
+ $el['ProgressTitle'].Text = "Telep`u{00ed}t`u{00e9}s folyamatban..."
+
+ Append-Log "ODT letoltes indul..."
+ $setupPath = Download-ODT
+ if (-not $setupPath) {
+ $el['ProgressTitle'].Text = "Telep`u{00ed}t`u{00e9}s sikertelen"
+ $el['DoneText'].Text = "Az ODT letoltese sikertelen."
+ $el['DoneText'].Foreground = $window.FindResource('ErrorBrush')
+ $el['DonePanel'].Visibility = 'Visible'
+ $el['BtnBack'].Visibility = 'Visible'
+ return
+ }
+
+ Append-Log "Konfiguracios XML generalasa..."
+ $xml = Generate-OdtXml
+ $odtFolder = Join-Path $env:TEMP 'InstaSoftODT'
+ $xmlPath = Join-Path $odtFolder 'configuration.xml'
+ [System.IO.File]::WriteAllText($xmlPath, $xml)
+ Append-Log "XML mentve: $xmlPath"
+
+ Append-Log "Office telepites inditasa..."
+ $exitCode = Run-Setup $setupPath $xmlPath
+
+ $el['ProgressBar'].IsIndeterminate = $false
+ $el['ProgressBar'].Value = 100
+
+ if ($exitCode -eq 0) {
+ $el['ProgressTitle'].Text = "Telep`u{00ed}t`u{00e9}s befejezve"
+ $el['DoneText'].Text = "Az Office sikeresen telepult!"
+ $el['DoneText'].Foreground = $window.FindResource('SuccessBrush')
+ if (-not $script:Config.ProductKey) {
+ $el['ActivateCard'].Visibility = 'Visible'
+ }
+ # Launch buttons
+ $el['LaunchBtns'].Children.Clear()
+ $apps = @(@('Word','WINWORD.EXE'),@('Excel','EXCEL.EXE'),@('PowerPoint','POWERPNT.EXE'),@('Outlook','OUTLOOK.EXE'))
+ foreach ($a in $apps) {
+ if ($script:Config.ExcludedApps -notcontains $a[0]) {
+ $btn = New-Object Windows.Controls.Button
+ $btn.Content = $a[0]
+ $btn.Style = $window.FindResource('SecBtn')
+ $btn.Padding = [Windows.Thickness]::new(18,8,18,8)
+ $btn.FontSize = 13
+ $btn.Margin = [Windows.Thickness]::new(0,0,8,0)
+ $btn.Tag = $a[1]
+ $btn.Add_Click({ param($s,$e)
+ try { Start-Process $s.Tag } catch { }
+ })
+ $el['LaunchBtns'].Children.Add($btn) | Out-Null
+ }
+ }
+ $el['LaunchCard'].Visibility = 'Visible'
+ } else {
+ $el['ProgressTitle'].Text = "Telep`u{00ed}t`u{00e9}s sikertelen"
+ $el['DoneText'].Text = "A telepites hibakoddal fejezoedoett be: $exitCode"
+ $el['DoneText'].Foreground = $window.FindResource('ErrorBrush')
+ }
+ Append-Log "setup.exe exit code: $exitCode"
+ $el['DonePanel'].Visibility = 'Visible'
+ $el['BtnNext'].Content = "Bez`u{00e1}r`u{00e1}s"
+ $el['BtnNext'].Visibility = 'Visible'
+ $script:CurrentPanel = 'done'
+}
+
+function Detect-Office {
+ $results = @()
+ # Click-to-Run
+ try {
+ $c2rKey = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' -ErrorAction Stop
+ $ids = $c2rKey.ProductReleaseIds -split ','
+ foreach ($id in $ids) {
+ $id = $id.Trim()
+ if ($id) {
+ $results += @{
+ DisplayName = "Microsoft Office Click-to-Run ($id)"
+ Version = $c2rKey.VersionToReport
+ ProductCode = $id
+ IsC2R = $true
+ }
+ }
+ }
+ } catch {}
+ # MSI
+ foreach ($path in @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall')) {
+ try {
+ Get-ChildItem $path -ErrorAction Stop | ForEach-Object {
+ $props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
+ if ($props.DisplayName -and $props.Publisher -match 'Microsoft' -and ($props.DisplayName -match 'Microsoft Office|Microsoft 365')) {
+ $isGuid = $_.PSChildName -match '^\{[0-9A-Fa-f\-]+\}$'
+ $results += @{
+ DisplayName = $props.DisplayName
+ Version = $props.DisplayVersion
+ ProductCode = if ($isGuid) { $_.PSChildName } else { $null }
+ IsC2R = $false
+ }
+ }
+ }
+ } catch {}
+ }
+ return $results
+}
+
+function Find-Ospp {
+ $paths = @(
+ "$env:ProgramFiles\Microsoft Office\root\Office16\OSPP.VBS",
+ "${env:ProgramFiles(x86)}\Microsoft Office\root\Office16\OSPP.VBS",
+ "$env:ProgramFiles\Microsoft Office\Office16\ospp.vbs",
+ "${env:ProgramFiles(x86)}\Microsoft Office\Office16\ospp.vbs"
+ )
+ # C2R registry path
+ try {
+ $c2r = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' -ErrorAction Stop).InstallationPath
+ if ($c2r) {
+ $paths = @(Join-Path $c2r 'root\Office16\OSPP.VBS') + @(Join-Path $c2r 'Office16\OSPP.VBS') + $paths
+ }
+ } catch {}
+ foreach ($p in $paths) {
+ if (Test-Path $p) { return $p }
+ }
+ return $null
+}
+
+function Build-LicenseCards {
+ $el['LicenseCards'].Children.Clear()
+ $osppPath = Find-Ospp
+ if (-not $osppPath) {
+ $tb = New-Object Windows.Controls.TextBlock
+ $tb.Text = "Az ospp.vbs nem talalhato. Nincs telepitett Office?"
+ $tb.FontSize = 14; $tb.Foreground = $window.FindResource('ErrorBrush')
+ $el['LicenseCards'].Children.Add($tb) | Out-Null
+ return
+ }
+ $output = & cscript //Nologo $osppPath /dstatus 2>&1 | Out-String
+ # Parse entries
+ $blocks = $output -split '-{10,}'
+ foreach ($block in $blocks) {
+ $block = $block.Trim()
+ if (-not $block -or $block -match '^---') { continue }
+ $nameMatch = [regex]::Match($block, 'LICENSE NAME:\s*(.+)')
+ $statusMatch = [regex]::Match($block, 'LICENSE STATUS:\s*(.+)')
+ $errorMatch = [regex]::Match($block, 'ERROR DESCRIPTION:\s*(.+)')
+ $keyMatch = [regex]::Match($block, 'Last 5 characters of installed product key:\s*(\S+)')
+ if ($keyMatch.Success) {
+ $last5 = $keyMatch.Groups[1].Value
+ $name = if ($nameMatch.Success) { $nameMatch.Groups[1].Value.Trim() } else { 'Ismeretlen licenc' }
+ $status = if ($errorMatch.Success) { $errorMatch.Groups[1].Value.Trim() } elseif ($statusMatch.Success) { $statusMatch.Groups[1].Value.Trim() } else { '' }
+
+ # Card
+ $border = New-Object Windows.Controls.Border
+ $border.Background = [Windows.Media.Brushes]::White
+ $border.BorderBrush = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(224,224,224))
+ $border.BorderThickness = [Windows.Thickness]::new(1)
+ $border.CornerRadius = [Windows.CornerRadius]::new(8)
+ $border.Padding = [Windows.Thickness]::new(16,10,16,10)
+ $border.Margin = [Windows.Thickness]::new(0,0,0,6)
+
+ $grid = New-Object Windows.Controls.Grid
+ $grid.ColumnDefinitions.Add((New-Object Windows.Controls.ColumnDefinition -Property @{Width='*'}))
+ $grid.ColumnDefinitions.Add((New-Object Windows.Controls.ColumnDefinition -Property @{Width='Auto'}))
+
+ $info = New-Object Windows.Controls.StackPanel
+ $t1 = New-Object Windows.Controls.TextBlock; $t1.Text = $name; $t1.FontSize = 13; $t1.FontWeight = 'SemiBold'
+ $t2 = New-Object Windows.Controls.TextBlock; $t2.Text = $status; $t2.FontSize = 11; $t2.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(102,102,102))
+ $t3 = New-Object Windows.Controls.TextBlock; $t3.Text = "Kulcs: *****-$last5"; $t3.FontSize = 11; $t3.FontFamily = 'Consolas'
+ $t3.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(102,102,102))
+ $info.Children.Add($t1) | Out-Null; $info.Children.Add($t2) | Out-Null; $info.Children.Add($t3) | Out-Null
+ [Windows.Controls.Grid]::SetColumn($info, 0)
+ $grid.Children.Add($info) | Out-Null
+
+ $btnPanel = New-Object Windows.Controls.StackPanel
+ $btnPanel.Orientation = 'Horizontal'
+ $btnPanel.VerticalAlignment = 'Center'
+
+ $rmBtn = New-Object Windows.Controls.Button
+ $rmBtn.Content = "Eltavolitas"
+ $rmBtn.Style = $window.FindResource('SecBtn')
+ $rmBtn.Padding = [Windows.Thickness]::new(14,6,14,6)
+ $rmBtn.FontSize = 12
+ $rmBtn.Tag = @{ Last5 = $last5; OsppPath = $osppPath }
+ $rmBtn.Add_Click({
+ param($s,$e)
+ $info = $s.Tag
+ & cscript //Nologo $info.OsppPath "/unpkey:$($info.Last5)" 2>&1 | Out-Null
+ Build-LicenseCards
+ })
+ $btnPanel.Children.Add($rmBtn) | Out-Null
+
+ [Windows.Controls.Grid]::SetColumn($btnPanel, 1)
+ $grid.Children.Add($btnPanel) | Out-Null
+
+ $border.Child = $grid
+ $el['LicenseCards'].Children.Add($border) | Out-Null
+ }
+ }
+ if ($el['LicenseCards'].Children.Count -eq 0) {
+ $tb = New-Object Windows.Controls.TextBlock
+ $tb.Text = "Nincs telepitett termekkulcs."
+ $tb.FontSize = 13; $tb.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(102,102,102))
+ $el['LicenseCards'].Children.Add($tb) | Out-Null
+ }
+}
+
+# ============================================================
+# EVENT HANDLERS
+# ============================================================
+
+# Welcome buttons
+$el['BtnInstall'].Add_Click({
+ $script:InstallIndex = 0
+ Build-LanguageCombo
+ Navigate-Install 0
+})
+
+$el['BtnRemoveWelcome'].Add_Click({
+ Show-Panel 'PanelRemove'
+ $el['BtnBack'].Visibility = 'Visible'
+ $el['BtnNext'].Visibility = 'Collapsed'
+ $el['StepDots'].Children.Clear()
+ $script:CurrentPanel = 'PanelRemove'
+ # Detect office
+ $el['RemoveList'].Children.Clear()
+ $detected = Detect-Office
+ if ($detected.Count -eq 0) {
+ $tb = New-Object Windows.Controls.TextBlock
+ $tb.Text = "Nem talalhato telepitett Office."
+ $tb.FontSize = 14; $tb.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(102,102,102))
+ $el['RemoveList'].Children.Add($tb) | Out-Null
+ $el['BtnDoRemove'].IsEnabled = $false
+ } else {
+ foreach ($o in $detected) {
+ $cb = New-Object Windows.Controls.CheckBox
+ $cb.Content = "$($o.DisplayName) ($($o.Version))"
+ $cb.IsChecked = $true
+ $cb.Style = $window.FindResource('FluentCB')
+ $cb.Tag = $o
+ $cb.Margin = [Windows.Thickness]::new(0,4,0,4)
+ $el['RemoveList'].Children.Add($cb) | Out-Null
+ }
+ }
+})
+
+$el['BtnLicenseWelcome'].Add_Click({
+ Show-Panel 'PanelLicense'
+ $el['BtnBack'].Visibility = 'Visible'
+ $el['BtnNext'].Visibility = 'Collapsed'
+ $el['StepDots'].Children.Clear()
+ $script:CurrentPanel = 'PanelLicense'
+ Build-LicenseCards
+})
+
+# Back button
+$el['BtnBack'].Add_Click({
+ $window.Height = 550
+ if ($script:CurrentPanel -eq 'done') {
+ $window.Close()
+ return
+ }
+ if ($script:InstallPanels -contains $script:CurrentPanel -and $script:InstallIndex -gt 0) {
+ Navigate-Install ($script:InstallIndex - 1)
+ } else {
+ Show-Panel 'PanelWelcome'
+ $script:CurrentPanel = 'PanelWelcome'
+ $el['BtnBack'].Visibility = 'Collapsed'
+ $el['BtnNext'].Visibility = 'Collapsed'
+ $el['StepDots'].Children.Clear()
+ }
+})
+
+# Next button
+$el['BtnNext'].Add_Click({
+ if ($script:CurrentPanel -eq 'done') {
+ $window.Close()
+ return
+ }
+ # Validate + advance
+ switch ($script:CurrentPanel) {
+ 'PanelVersion' {
+ $script:Config.Version = Get-SelectedVersion
+ Build-EditionCards
+ Navigate-Install 1
+ }
+ 'PanelEdition' {
+ $script:Config.Edition = Get-SelectedEdition
+ Build-AppChecks
+ Navigate-Install 2
+ }
+ 'PanelConfig' {
+ $script:Config.Architecture = if ($el['Rb64'].IsChecked) { '64' } else { '32' }
+ $sel = $el['CbLanguage'].SelectedItem
+ if ($sel) { $script:Config.Language = $sel.Tag }
+ $script:Config.ExcludedApps = @()
+ foreach ($child in $el['AppChecks'].Children) {
+ if ($child -is [Windows.Controls.CheckBox] -and (-not $child.IsChecked -or -not $child.IsEnabled)) {
+ $script:Config.ExcludedApps += $child.Tag
+ }
+ }
+ Navigate-Install 3
+ }
+ 'PanelKey' {
+ if ($el['CbSkipKey'].IsChecked) {
+ $script:Config.ProductKey = ''
+ } else {
+ $script:Config.ProductKey = Get-ProductKey
+ }
+ Build-Summary
+ Navigate-Install 4
+ }
+ 'PanelSummary' {
+ Start-Installation
+ }
+ }
+})
+
+# License refresh
+$el['BtnLicRefresh'].Add_Click({ Build-LicenseCards })
+
+# Key auto-tab
+$keyBoxes = @($el['K1'],$el['K2'],$el['K3'],$el['K4'],$el['K5'])
+for ($i = 0; $i -lt $keyBoxes.Count; $i++) {
+ $box = $keyBoxes[$i]
+ $idx = $i
+ $box.Add_TextChanged({
+ param($s,$e)
+ if ($s.Text.Length -eq 5 -and $idx -lt 4) {
+ $keyBoxes[$idx + 1].Focus()
+ }
+ }.GetNewClosure())
+ # Paste handler
+ [Windows.DataObject]::AddPastingHandler($box, [Windows.DataObjectPastingEventHandler]{
+ param($s,$e)
+ if ($e.DataObject.GetDataPresent([string])) {
+ $pasted = $e.DataObject.GetData([string])
+ $clean = $pasted -replace '[^A-Za-z0-9]',''
+ if ($clean.Length -gt 5) {
+ $e.CancelCommand()
+ for ($j = 0; $j -lt 5; $j++) {
+ $start = $j * 5
+ if ($start -lt $clean.Length) {
+ $len = [Math]::Min(5, $clean.Length - $start)
+ $keyBoxes[$j].Text = $clean.Substring($start, $len).ToUpper()
+ }
+ }
+ $keyBoxes[4].Focus()
+ }
+ }
+ })
+}
+
+# Skip key checkbox
+$el['CbSkipKey'].Add_Checked({ foreach ($b in $keyBoxes) { $b.IsEnabled = $false; $b.Text = '' } })
+$el['CbSkipKey'].Add_Unchecked({ foreach ($b in $keyBoxes) { $b.IsEnabled = $true } })
+
+# ============================================================
+# RUN
+# ============================================================
+$window.ShowDialog() | Out-Null
diff --git a/App.xaml b/src/App.xaml
similarity index 100%
rename from App.xaml
rename to src/App.xaml
diff --git a/App.xaml.cs b/src/App.xaml.cs
similarity index 100%
rename from App.xaml.cs
rename to src/App.xaml.cs
diff --git a/InstaSoftOfficeTool.csproj b/src/InstaSoftOfficeTool.csproj
similarity index 88%
rename from InstaSoftOfficeTool.csproj
rename to src/InstaSoftOfficeTool.csproj
index e1f62be..28b3efa 100644
--- a/InstaSoftOfficeTool.csproj
+++ b/src/InstaSoftOfficeTool.csproj
@@ -9,9 +9,9 @@
InstaSoft Zrt.
InstaSoft Office Tool
Copyright (c) InstaSoft Zrt. 2026
- 1.1.4
- 1.1.4.0
- 1.1.4.0
+ 1.1.5
+ 1.1.5.0
+ 1.1.5.0
app.manifest
latest
diff --git a/MainWindow.xaml b/src/MainWindow.xaml
similarity index 98%
rename from MainWindow.xaml
rename to src/MainWindow.xaml
index d66c7bd..586c21e 100644
--- a/MainWindow.xaml
+++ b/src/MainWindow.xaml
@@ -45,7 +45,7 @@
Foreground="{StaticResource TextSecondaryBrush}" Margin="0,-2,0,0"/>
-
diff --git a/MainWindow.xaml.cs b/src/MainWindow.xaml.cs
similarity index 100%
rename from MainWindow.xaml.cs
rename to src/MainWindow.xaml.cs
diff --git a/Models/InstallConfig.cs b/src/Models/InstallConfig.cs
similarity index 100%
rename from Models/InstallConfig.cs
rename to src/Models/InstallConfig.cs
diff --git a/Models/InstalledOffice.cs b/src/Models/InstalledOffice.cs
similarity index 100%
rename from Models/InstalledOffice.cs
rename to src/Models/InstalledOffice.cs
diff --git a/Models/OfficeEdition.cs b/src/Models/OfficeEdition.cs
similarity index 100%
rename from Models/OfficeEdition.cs
rename to src/Models/OfficeEdition.cs
diff --git a/Models/OfficeVersion.cs b/src/Models/OfficeVersion.cs
similarity index 100%
rename from Models/OfficeVersion.cs
rename to src/Models/OfficeVersion.cs
diff --git a/Pages/ConfigPage.xaml b/src/Pages/ConfigPage.xaml
similarity index 100%
rename from Pages/ConfigPage.xaml
rename to src/Pages/ConfigPage.xaml
diff --git a/Pages/ConfigPage.xaml.cs b/src/Pages/ConfigPage.xaml.cs
similarity index 100%
rename from Pages/ConfigPage.xaml.cs
rename to src/Pages/ConfigPage.xaml.cs
diff --git a/Pages/ConfirmDialog.xaml b/src/Pages/ConfirmDialog.xaml
similarity index 100%
rename from Pages/ConfirmDialog.xaml
rename to src/Pages/ConfirmDialog.xaml
diff --git a/Pages/ConfirmDialog.xaml.cs b/src/Pages/ConfirmDialog.xaml.cs
similarity index 100%
rename from Pages/ConfirmDialog.xaml.cs
rename to src/Pages/ConfirmDialog.xaml.cs
diff --git a/Pages/EditionPage.xaml b/src/Pages/EditionPage.xaml
similarity index 100%
rename from Pages/EditionPage.xaml
rename to src/Pages/EditionPage.xaml
diff --git a/Pages/EditionPage.xaml.cs b/src/Pages/EditionPage.xaml.cs
similarity index 100%
rename from Pages/EditionPage.xaml.cs
rename to src/Pages/EditionPage.xaml.cs
diff --git a/Pages/PreInstallCheckPage.xaml b/src/Pages/PreInstallCheckPage.xaml
similarity index 100%
rename from Pages/PreInstallCheckPage.xaml
rename to src/Pages/PreInstallCheckPage.xaml
diff --git a/Pages/PreInstallCheckPage.xaml.cs b/src/Pages/PreInstallCheckPage.xaml.cs
similarity index 100%
rename from Pages/PreInstallCheckPage.xaml.cs
rename to src/Pages/PreInstallCheckPage.xaml.cs
diff --git a/Pages/ProductKeyDialog.xaml b/src/Pages/ProductKeyDialog.xaml
similarity index 100%
rename from Pages/ProductKeyDialog.xaml
rename to src/Pages/ProductKeyDialog.xaml
diff --git a/Pages/ProductKeyDialog.xaml.cs b/src/Pages/ProductKeyDialog.xaml.cs
similarity index 100%
rename from Pages/ProductKeyDialog.xaml.cs
rename to src/Pages/ProductKeyDialog.xaml.cs
diff --git a/Pages/ProductKeyPage.xaml b/src/Pages/ProductKeyPage.xaml
similarity index 100%
rename from Pages/ProductKeyPage.xaml
rename to src/Pages/ProductKeyPage.xaml
diff --git a/Pages/ProductKeyPage.xaml.cs b/src/Pages/ProductKeyPage.xaml.cs
similarity index 100%
rename from Pages/ProductKeyPage.xaml.cs
rename to src/Pages/ProductKeyPage.xaml.cs
diff --git a/Pages/ProgressPage.xaml b/src/Pages/ProgressPage.xaml
similarity index 100%
rename from Pages/ProgressPage.xaml
rename to src/Pages/ProgressPage.xaml
diff --git a/Pages/ProgressPage.xaml.cs b/src/Pages/ProgressPage.xaml.cs
similarity index 100%
rename from Pages/ProgressPage.xaml.cs
rename to src/Pages/ProgressPage.xaml.cs
diff --git a/Pages/RemovePage.xaml b/src/Pages/RemovePage.xaml
similarity index 100%
rename from Pages/RemovePage.xaml
rename to src/Pages/RemovePage.xaml
diff --git a/Pages/RemovePage.xaml.cs b/src/Pages/RemovePage.xaml.cs
similarity index 100%
rename from Pages/RemovePage.xaml.cs
rename to src/Pages/RemovePage.xaml.cs
diff --git a/Pages/SummaryPage.xaml b/src/Pages/SummaryPage.xaml
similarity index 100%
rename from Pages/SummaryPage.xaml
rename to src/Pages/SummaryPage.xaml
diff --git a/Pages/SummaryPage.xaml.cs b/src/Pages/SummaryPage.xaml.cs
similarity index 100%
rename from Pages/SummaryPage.xaml.cs
rename to src/Pages/SummaryPage.xaml.cs
diff --git a/Pages/TroubleshootPage.xaml b/src/Pages/TroubleshootPage.xaml
similarity index 100%
rename from Pages/TroubleshootPage.xaml
rename to src/Pages/TroubleshootPage.xaml
diff --git a/Pages/TroubleshootPage.xaml.cs b/src/Pages/TroubleshootPage.xaml.cs
similarity index 100%
rename from Pages/TroubleshootPage.xaml.cs
rename to src/Pages/TroubleshootPage.xaml.cs
diff --git a/Pages/VersionPage.xaml b/src/Pages/VersionPage.xaml
similarity index 100%
rename from Pages/VersionPage.xaml
rename to src/Pages/VersionPage.xaml
diff --git a/Pages/VersionPage.xaml.cs b/src/Pages/VersionPage.xaml.cs
similarity index 100%
rename from Pages/VersionPage.xaml.cs
rename to src/Pages/VersionPage.xaml.cs
diff --git a/Pages/WelcomePage.xaml b/src/Pages/WelcomePage.xaml
similarity index 100%
rename from Pages/WelcomePage.xaml
rename to src/Pages/WelcomePage.xaml
diff --git a/Pages/WelcomePage.xaml.cs b/src/Pages/WelcomePage.xaml.cs
similarity index 100%
rename from Pages/WelcomePage.xaml.cs
rename to src/Pages/WelcomePage.xaml.cs
diff --git a/Resources/app.ico b/src/Resources/app.ico
similarity index 100%
rename from Resources/app.ico
rename to src/Resources/app.ico
diff --git a/Services/LicenseManager.cs b/src/Services/LicenseManager.cs
similarity index 100%
rename from Services/LicenseManager.cs
rename to src/Services/LicenseManager.cs
diff --git a/Services/OdtDownloader.cs b/src/Services/OdtDownloader.cs
similarity index 100%
rename from Services/OdtDownloader.cs
rename to src/Services/OdtDownloader.cs
diff --git a/Services/OdtXmlGenerator.cs b/src/Services/OdtXmlGenerator.cs
similarity index 100%
rename from Services/OdtXmlGenerator.cs
rename to src/Services/OdtXmlGenerator.cs
diff --git a/Services/OfficeDetector.cs b/src/Services/OfficeDetector.cs
similarity index 100%
rename from Services/OfficeDetector.cs
rename to src/Services/OfficeDetector.cs
diff --git a/Services/ProcessRunner.cs b/src/Services/ProcessRunner.cs
similarity index 100%
rename from Services/ProcessRunner.cs
rename to src/Services/ProcessRunner.cs
diff --git a/Styles/ButtonStyles.xaml b/src/Styles/ButtonStyles.xaml
similarity index 100%
rename from Styles/ButtonStyles.xaml
rename to src/Styles/ButtonStyles.xaml
diff --git a/Styles/ControlStyles.xaml b/src/Styles/ControlStyles.xaml
similarity index 100%
rename from Styles/ControlStyles.xaml
rename to src/Styles/ControlStyles.xaml
diff --git a/Styles/FluentTheme.xaml b/src/Styles/FluentTheme.xaml
similarity index 100%
rename from Styles/FluentTheme.xaml
rename to src/Styles/FluentTheme.xaml
diff --git a/app.manifest b/src/app.manifest
similarity index 100%
rename from app.manifest
rename to src/app.manifest