v1.10 — Activation with key dialog, auto-refresh, Access restriction, fixes
- Product key dialog: Fluent overlay with 5-field input + paste support
- Activate button on each license card asks for key first (/inpkey + /act)
- Auto-refresh lists after removal/activation on all pages
- Access checkbox disabled for Standard and Home & Business editions
- MSI uninstall: only accept {GUID} product codes (fix msiexec help popup)
- Window height properly resets when Details expander is collapsed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,9 @@
|
||||
<Company>InstaSoft Zrt.</Company>
|
||||
<Product>InstaSoft Office Tool</Product>
|
||||
<Copyright>Copyright (c) InstaSoft Zrt. 2026</Copyright>
|
||||
<Version>1.0.9</Version>
|
||||
<AssemblyVersion>1.0.9.0</AssemblyVersion>
|
||||
<FileVersion>1.0.9.0</FileVersion>
|
||||
<Version>1.1.0</Version>
|
||||
<AssemblyVersion>1.1.0.0</AssemblyVersion>
|
||||
<FileVersion>1.1.0.0</FileVersion>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
Foreground="{StaticResource TextSecondaryBrush}" Margin="0,-2,0,0"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<TextBlock Text="v1.09" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
<TextBlock Text="v1.10" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
FontSize="12" Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -76,6 +76,8 @@
|
||||
</Grid>
|
||||
|
||||
<!-- Confirm dialog overlay -->
|
||||
<pages:ConfirmDialog x:Name="Dialog" Grid.RowSpan="3"/>
|
||||
<pages:ConfirmDialog x:Name="Dialog"/>
|
||||
<!-- Product key dialog overlay -->
|
||||
<pages:ProductKeyDialog x:Name="KeyDialog"/>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -202,6 +202,11 @@ namespace InstaSoftOfficeTool
|
||||
return Dialog.ShowAsync(title, message, confirmText, cancelText, type);
|
||||
}
|
||||
|
||||
public Task<string> AskProductKeyAsync()
|
||||
{
|
||||
return KeyDialog.ShowAsync();
|
||||
}
|
||||
|
||||
public void ShowBackToHomeButton()
|
||||
{
|
||||
BtnBack.Content = "\u2190 F\u0151oldal";
|
||||
|
||||
@@ -53,10 +53,15 @@ namespace InstaSoftOfficeTool.Pages
|
||||
? !_config.ExcludedApps.Contains(app.Id)
|
||||
: app.DefaultChecked;
|
||||
|
||||
bool isProPlus = _config.Edition != null &&
|
||||
_config.Edition.ProductId.Contains("ProPlus");
|
||||
bool accessOnly = app.Id == "Access";
|
||||
|
||||
var cb = new CheckBox
|
||||
{
|
||||
Content = app.DisplayName,
|
||||
IsChecked = isChecked,
|
||||
IsChecked = accessOnly && !isProPlus ? false : isChecked,
|
||||
IsEnabled = !(accessOnly && !isProPlus),
|
||||
Tag = app.Id,
|
||||
Style = (Style)FindResource("FluentCheckBox"),
|
||||
Margin = new Thickness(0, 4, 24, 4),
|
||||
@@ -79,7 +84,7 @@ namespace InstaSoftOfficeTool.Pages
|
||||
_config.ExcludedApps.Clear();
|
||||
foreach (var cb in _appCheckBoxes)
|
||||
{
|
||||
if (cb.IsChecked != true)
|
||||
if (cb.IsChecked != true || !cb.IsEnabled)
|
||||
{
|
||||
_config.ExcludedApps.Add((string)cb.Tag);
|
||||
}
|
||||
|
||||
69
Pages/ProductKeyDialog.xaml
Normal file
69
Pages/ProductKeyDialog.xaml
Normal file
@@ -0,0 +1,69 @@
|
||||
<UserControl x:Class="InstaSoftOfficeTool.Pages.ProductKeyDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid>
|
||||
<Border Background="#66000000" MouseDown="Backdrop_MouseDown"/>
|
||||
|
||||
<Border Background="{StaticResource CardBrush}" CornerRadius="12"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
MinWidth="480" Padding="28,24">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="8" BlurRadius="32" Opacity="0.2" Color="Black"/>
|
||||
</Border.Effect>
|
||||
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="22"
|
||||
Foreground="{StaticResource AccentBrush}"
|
||||
VerticalAlignment="Center" Margin="0,0,12,0"/>
|
||||
<TextBlock Text="Termékkulcs megadása" FontSize="18" FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Adja meg a 25 karakteres termékkulcsot az aktiváláshoz."
|
||||
FontSize="13" Foreground="{StaticResource TextSecondaryBrush}"
|
||||
Margin="0,0,0,16"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,20">
|
||||
<TextBox x:Name="DKey1" Style="{StaticResource FluentTextBox}"
|
||||
Width="76" MaxLength="5" CharacterCasing="Upper"
|
||||
FontFamily="Consolas" FontSize="15" TextAlignment="Center"
|
||||
TextChanged="KeyBox_TextChanged"/>
|
||||
<TextBlock Text="-" FontSize="18" VerticalAlignment="Center" Margin="4,0"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBox x:Name="DKey2" Style="{StaticResource FluentTextBox}"
|
||||
Width="76" MaxLength="5" CharacterCasing="Upper"
|
||||
FontFamily="Consolas" FontSize="15" TextAlignment="Center"
|
||||
TextChanged="KeyBox_TextChanged"/>
|
||||
<TextBlock Text="-" FontSize="18" VerticalAlignment="Center" Margin="4,0"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBox x:Name="DKey3" Style="{StaticResource FluentTextBox}"
|
||||
Width="76" MaxLength="5" CharacterCasing="Upper"
|
||||
FontFamily="Consolas" FontSize="15" TextAlignment="Center"
|
||||
TextChanged="KeyBox_TextChanged"/>
|
||||
<TextBlock Text="-" FontSize="18" VerticalAlignment="Center" Margin="4,0"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBox x:Name="DKey4" Style="{StaticResource FluentTextBox}"
|
||||
Width="76" MaxLength="5" CharacterCasing="Upper"
|
||||
FontFamily="Consolas" FontSize="15" TextAlignment="Center"
|
||||
TextChanged="KeyBox_TextChanged"/>
|
||||
<TextBlock Text="-" FontSize="18" VerticalAlignment="Center" Margin="4,0"
|
||||
Foreground="{StaticResource TextSecondaryBrush}"/>
|
||||
<TextBox x:Name="DKey5" Style="{StaticResource FluentTextBox}"
|
||||
Width="76" MaxLength="5" CharacterCasing="Upper"
|
||||
FontFamily="Consolas" FontSize="15" TextAlignment="Center"
|
||||
TextChanged="KeyBox_TextChanged"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="Mégse" Style="{StaticResource SecondaryButton}"
|
||||
Click="BtnCancel_Click" Margin="0,0,8,0"/>
|
||||
<Button x:Name="BtnOk" Content="Aktiválás" Style="{StaticResource PrimaryButton}"
|
||||
Click="BtnOk_Click" IsEnabled="False"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
124
Pages/ProductKeyDialog.xaml.cs
Normal file
124
Pages/ProductKeyDialog.xaml.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace InstaSoftOfficeTool.Pages
|
||||
{
|
||||
public partial class ProductKeyDialog : UserControl
|
||||
{
|
||||
private TaskCompletionSource<string> _tcs;
|
||||
private readonly TextBox[] _keyBoxes;
|
||||
private bool _suppress;
|
||||
|
||||
public ProductKeyDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
_keyBoxes = new[] { DKey1, DKey2, DKey3, DKey4, DKey5 };
|
||||
|
||||
foreach (var box in _keyBoxes)
|
||||
{
|
||||
DataObject.AddPastingHandler(box, OnPaste);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<string> ShowAsync()
|
||||
{
|
||||
foreach (var box in _keyBoxes) box.Text = "";
|
||||
BtnOk.IsEnabled = false;
|
||||
Visibility = Visibility.Visible;
|
||||
DKey1.Focus();
|
||||
_tcs = new TaskCompletionSource<string>();
|
||||
return _tcs.Task;
|
||||
}
|
||||
|
||||
private string GetFullKey()
|
||||
{
|
||||
return string.Join("-", DKey1.Text, DKey2.Text, DKey3.Text, DKey4.Text, DKey5.Text).ToUpper();
|
||||
}
|
||||
|
||||
private bool IsKeyComplete()
|
||||
{
|
||||
foreach (var box in _keyBoxes)
|
||||
{
|
||||
if (box.Text.Length != 5 || !Regex.IsMatch(box.Text, "^[A-Za-z0-9]{5}$"))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void KeyBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
var tb = (TextBox)sender;
|
||||
|
||||
var cleaned = Regex.Replace(tb.Text, "[^A-Za-z0-9]", "");
|
||||
if (cleaned != tb.Text)
|
||||
{
|
||||
_suppress = true;
|
||||
tb.Text = cleaned;
|
||||
tb.CaretIndex = cleaned.Length;
|
||||
_suppress = false;
|
||||
}
|
||||
|
||||
if (tb.Text.Length == 5)
|
||||
{
|
||||
for (int i = 0; i < _keyBoxes.Length - 1; i++)
|
||||
{
|
||||
if (_keyBoxes[i] == tb)
|
||||
{
|
||||
_keyBoxes[i + 1].Focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BtnOk.IsEnabled = IsKeyComplete();
|
||||
}
|
||||
|
||||
private void OnPaste(object sender, DataObjectPastingEventArgs e)
|
||||
{
|
||||
if (!e.DataObject.GetDataPresent(typeof(string))) return;
|
||||
var pasted = (string)e.DataObject.GetData(typeof(string));
|
||||
var allAlphaNum = Regex.Replace(pasted, "[^A-Za-z0-9]", "");
|
||||
|
||||
if (allAlphaNum.Length <= 5) return;
|
||||
|
||||
e.CancelCommand();
|
||||
_suppress = true;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
int start = i * 5;
|
||||
if (start < allAlphaNum.Length)
|
||||
{
|
||||
int len = Math.Min(5, allAlphaNum.Length - start);
|
||||
_keyBoxes[i].Text = allAlphaNum.Substring(start, len).ToUpper();
|
||||
}
|
||||
}
|
||||
_suppress = false;
|
||||
_keyBoxes[4].Focus();
|
||||
_keyBoxes[4].CaretIndex = _keyBoxes[4].Text.Length;
|
||||
BtnOk.IsEnabled = IsKeyComplete();
|
||||
}
|
||||
|
||||
private void BtnOk_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
_tcs?.TrySetResult(GetFullKey());
|
||||
}
|
||||
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
_tcs?.TrySetResult(null);
|
||||
}
|
||||
|
||||
private void Backdrop_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
_tcs?.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,13 @@ namespace InstaSoftOfficeTool.Pages
|
||||
}
|
||||
|
||||
AppendLog("");
|
||||
AppendLog("K\u00e9sz.");
|
||||
AppendLog("K\u00e9sz. Lista friss\u00edt\u00e9se...");
|
||||
DetectOffice();
|
||||
|
||||
if (_detected.Count == 0)
|
||||
{
|
||||
AppendLog("Nincs t\u00f6bb telep\u00edtett Office.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -151,6 +157,7 @@ namespace InstaSoftOfficeTool.Pages
|
||||
AppendLog(ex.StackTrace);
|
||||
}
|
||||
|
||||
BtnRemove.IsEnabled = _detected.Count > 0;
|
||||
_main.ShowCloseButton();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<!-- Title -->
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,16">
|
||||
<TextBlock Text="Licenc-kezelés" FontSize="24" FontWeight="Light"/>
|
||||
<TextBlock Text="Office licenc állapot lekérdezése és termékkulcsok kezelése"
|
||||
<TextBlock Text="Office licenc állapot lekérdezése, aktiválás és termékkulcsok kezelése"
|
||||
FontSize="14" Foreground="{StaticResource TextSecondaryBrush}" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace InstaSoftOfficeTool.Pages
|
||||
BtnRemoveAll.Visibility = Visibility.Collapsed;
|
||||
BtnRefresh.IsEnabled = false;
|
||||
|
||||
// Loading indicator
|
||||
var loadingText = new TextBlock
|
||||
{
|
||||
Text = "Keres\u00e9s folyamatban...",
|
||||
@@ -164,18 +163,38 @@ namespace InstaSoftOfficeTool.Pages
|
||||
Grid.SetColumn(infoPanel, 0);
|
||||
grid.Children.Add(infoPanel);
|
||||
|
||||
// Right side: Activate + Remove buttons
|
||||
var btnPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
var activateBtn = new Button
|
||||
{
|
||||
Content = "Aktiv\u00e1l\u00e1s",
|
||||
Style = (Style)FindResource("PrimaryButton"),
|
||||
Padding = new Thickness(14, 6, 14, 6),
|
||||
FontSize = 12,
|
||||
Tag = entry.Last5
|
||||
};
|
||||
activateBtn.Click += BtnActivateSingle_Click;
|
||||
btnPanel.Children.Add(activateBtn);
|
||||
|
||||
var removeBtn = new Button
|
||||
{
|
||||
Content = "Elt\u00e1vol\u00edt\u00e1s",
|
||||
Style = (Style)FindResource("SecondaryButton"),
|
||||
Padding = new Thickness(14, 6, 14, 6),
|
||||
FontSize = 12,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Tag = entry.Last5
|
||||
Tag = entry.Last5,
|
||||
Margin = new Thickness(6, 0, 0, 0)
|
||||
};
|
||||
removeBtn.Click += BtnRemoveSingle_Click;
|
||||
Grid.SetColumn(removeBtn, 1);
|
||||
grid.Children.Add(removeBtn);
|
||||
btnPanel.Children.Add(removeBtn);
|
||||
|
||||
Grid.SetColumn(btnPanel, 1);
|
||||
grid.Children.Add(btnPanel);
|
||||
|
||||
return new Border
|
||||
{
|
||||
@@ -189,6 +208,37 @@ namespace InstaSoftOfficeTool.Pages
|
||||
};
|
||||
}
|
||||
|
||||
private async void BtnActivateSingle_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string key = await _main.AskProductKeyAsync();
|
||||
if (string.IsNullOrEmpty(key)) return;
|
||||
|
||||
var btn = (Button)sender;
|
||||
btn.IsEnabled = false;
|
||||
btn.Content = "...";
|
||||
|
||||
DetailsExpander.IsExpanded = true;
|
||||
OutputText.Text = "Term\u00e9kkulcs telep\u00edt\u00e9se: " + key + "\n";
|
||||
|
||||
try
|
||||
{
|
||||
string inpResult = await _licenseManager.InstallKeyAsync(key);
|
||||
OutputText.Text += inpResult + "\n";
|
||||
|
||||
OutputText.Text += "Aktiv\u00e1l\u00e1s...\n";
|
||||
string actResult = await _licenseManager.ActivateAsync();
|
||||
OutputText.Text += actResult;
|
||||
|
||||
await RefreshStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OutputText.Text += "\nHiba: " + ex.Message;
|
||||
btn.IsEnabled = true;
|
||||
btn.Content = "Aktiv\u00e1l\u00e1s";
|
||||
}
|
||||
}
|
||||
|
||||
private async void BtnRemoveSingle_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var btn = (Button)sender;
|
||||
@@ -250,17 +300,12 @@ namespace InstaSoftOfficeTool.Pages
|
||||
|
||||
private void DetailsExpander_Expanded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Grow window to fit details
|
||||
var window = Window.GetWindow(this);
|
||||
if (window != null && window.Height < 680)
|
||||
window.Height = 680;
|
||||
_main.Height = 680;
|
||||
}
|
||||
|
||||
private void DetailsExpander_Collapsed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = Window.GetWindow(this);
|
||||
if (window != null)
|
||||
window.Height = 550;
|
||||
_main.Height = 550;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,5 +161,25 @@ namespace InstaSoftOfficeTool.Services
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using InstaSoftOfficeTool.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
@@ -92,11 +93,14 @@ namespace InstaSoftOfficeTool.Services
|
||||
!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 = subKeyName,
|
||||
ProductCode = isGuid ? subKeyName : null,
|
||||
IsClickToRun = false,
|
||||
IsSelected = true
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user