diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..ec0526e
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,42 @@
+name: Build
+
+on:
+ push:
+ branches:
+ - master
+ - v2-modernization
+ pull_request:
+ branches:
+ - master
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET 10
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Restore
+ run: dotnet restore PrinterManagerNativeTool.sln
+
+ - name: Build
+ run: dotnet build PrinterManagerNativeTool.sln -c Release --no-restore
+
+ - name: Publish portable x64
+ run: dotnet publish PrinterManagerNativeTool/PrinterManagerNativeTool.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true --no-restore -o artifacts/win-x64
+
+ - name: Upload portable executable
+ uses: actions/upload-artifact@v4
+ with:
+ name: PrinterManager-win-x64
+ path: artifacts/win-x64/PrinterManagerNativeTool.exe
+ if-no-files-found: error
diff --git a/PrinterManagerNativeTool/App.xaml b/PrinterManagerNativeTool/App.xaml
new file mode 100644
index 0000000..5efad71
--- /dev/null
+++ b/PrinterManagerNativeTool/App.xaml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PrinterManagerNativeTool/App.xaml.cs b/PrinterManagerNativeTool/App.xaml.cs
new file mode 100644
index 0000000..27799de
--- /dev/null
+++ b/PrinterManagerNativeTool/App.xaml.cs
@@ -0,0 +1,64 @@
+using System.Windows;
+using PrinterManagerNativeTool.Services;
+
+namespace PrinterManagerNativeTool;
+
+public partial class App : Application
+{
+ protected override async void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+
+ if (e.Args.Length >= 2 &&
+ string.Equals(e.Args[0], "--admin", StringComparison.OrdinalIgnoreCase))
+ {
+ await RunAdministrativeActionAsync(e.Args[1]);
+ Shutdown();
+ return;
+ }
+
+ var window = new MainWindow();
+ MainWindow = window;
+ window.Show();
+ }
+
+ private static async Task RunAdministrativeActionAsync(string action)
+ {
+ var spooler = new SpoolerService();
+
+ try
+ {
+ switch (action.ToLowerInvariant())
+ {
+ case "restart-spooler":
+ await spooler.RestartAsync();
+ MessageBox.Show(
+ "Spooler di stampa riavviato correttamente.",
+ "Printer Manager",
+ MessageBoxButton.OK,
+ MessageBoxImage.Information);
+ break;
+
+ case "clear-spooler":
+ await spooler.ClearAsync();
+ MessageBox.Show(
+ "Coda spooler pulita correttamente.",
+ "Printer Manager",
+ MessageBoxButton.OK,
+ MessageBoxImage.Information);
+ break;
+
+ default:
+ throw new ArgumentException($"Azione amministrativa sconosciuta: {action}");
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ ex.Message,
+ "Printer Manager - Errore",
+ MessageBoxButton.OK,
+ MessageBoxImage.Error);
+ }
+ }
+}
diff --git a/PrinterManagerNativeTool/Form1.cs b/PrinterManagerNativeTool/Form1.cs
deleted file mode 100644
index 203afe2..0000000
--- a/PrinterManagerNativeTool/Form1.cs
+++ /dev/null
@@ -1,200 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Drawing;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-
-
-namespace PrinterManagerNativeTool
-{
- public partial class Form1 : Form
- {
- private ListBox lstPrinters;
- private Button btnRicarica;
- private Button btnRiavviaSpooler;
- private Button btnSvuotaSpooler;
- private Label lblAdminWarning;
- private ContextMenuStrip contextMenu;
-
- public Form1()
- {
- InitializeComponent();
- _ = CaricaStampanti();
- }
-
- private void InitializeComponent()
- {
- this.lstPrinters = new ListBox();
- this.btnRicarica = new Button();
- this.btnRiavviaSpooler = new Button();
- this.btnSvuotaSpooler = new Button();
- this.lblAdminWarning = new Label();
- this.contextMenu = new ContextMenuStrip();
-
- //
- // lstPrinters
- //
- this.lstPrinters.Location = new Point(10, 10);
- this.lstPrinters.Size = new Size(380, 250);
- this.lstPrinters.ContextMenuStrip = this.contextMenu;
- this.lstPrinters.MouseDown += LstPrinters_MouseDown;
-
- //
- // btnRicarica
- //
- this.btnRicarica.Text = "Ricarica stampanti";
- this.btnRicarica.Location = new Point(10, 270);
- this.btnRicarica.Size = new Size(380, 30);
- this.btnRicarica.Click += BtnLoad_Click;
-
- //
- // btnRiavviaSpooler
- //
- this.btnRiavviaSpooler.Text = "Riavvia spooler";
- this.btnRiavviaSpooler.Location = new Point(10, 310);
- this.btnRiavviaSpooler.Size = new Size(185, 30);
- this.btnRiavviaSpooler.Click += BtnRiavviaSpooler_Click;
-
- //
- // btnSvuotaSpooler
- //
- this.btnSvuotaSpooler.Text = "Svuota spooler";
- this.btnSvuotaSpooler.Location = new Point(205, 310);
- this.btnSvuotaSpooler.Size = new Size(185, 30);
- this.btnSvuotaSpooler.Click += BtnSvuotaSpooler_Click;
-
- //
- // lblAdminWarning
- //
- this.lblAdminWarning.Text = "⚠ Alcune funzioni richiedono i permessi da amministratore.";
- this.lblAdminWarning.Font = new Font("Segoe UI", 7F, FontStyle.Italic);
- this.lblAdminWarning.ForeColor = Color.Gray;
- this.lblAdminWarning.AutoSize = true;
- this.lblAdminWarning.Location = new Point(10, 345);
-
- //
- // contextMenu
- //
- var preferenze = new ToolStripMenuItem("Preferenze di stampa");
- preferenze.Click += (s, e) => ApriPreferenze();
-
- var proprieta = new ToolStripMenuItem("Proprietà stampante...");
- proprieta.Click += (s, e) => ApriProprietaStampante();
-
- var spooler = new ToolStripMenuItem("Apri spooler");
- spooler.Click += (s, e) => ApriCartellaSpooler();
-
- var predefinita = new ToolStripMenuItem("Imposta come predefinita");
- predefinita.Click += (s, e) => ImpostaComePredefinita();
-
- contextMenu.Items.Add(preferenze);
- contextMenu.Items.Add(proprieta);
- contextMenu.Items.Add(spooler);
- contextMenu.Items.Add(predefinita);
-
- //
- // Form1
- //
- this.Text = "Printer Manager Tool";
- this.ClientSize = new Size(400, 370);
- this.Controls.Add(lstPrinters);
- this.Controls.Add(btnRicarica);
- this.Controls.Add(btnRiavviaSpooler);
- this.Controls.Add(btnSvuotaSpooler);
- this.Controls.Add(lblAdminWarning);
- }
-
- private async void BtnLoad_Click(object sender, EventArgs e)
- {
- await CaricaStampanti();
- }
-
- private async Task CaricaStampanti()
- {
- lstPrinters.Items.Clear();
- lstPrinters.Items.Add("Caricamento in corso...");
-
- List printers = await Task.Run(() =>
- {
- return PrinterHelper.EnumPrintersMultiThread()
- .OrderBy(p => p.PrinterName, StringComparer.CurrentCultureIgnoreCase)
- .ToList();
- });
-
- lstPrinters.Items.Clear();
- foreach (var p in printers)
- {
- string stato = p.IsOffline ? "[Offline]" : "[Pronta]";
- lstPrinters.Items.Add($"{p.PrinterName} {stato}");
- }
- }
-
- private void BtnRiavviaSpooler_Click(object sender, EventArgs e)
- {
- EseguiComeAmministratore("riavviaSpooler");
- }
-
- private void BtnSvuotaSpooler_Click(object sender, EventArgs e)
- {
- EseguiComeAmministratore("svuotaSpooler");
- }
-
- private void EseguiComeAmministratore(string argomento)
- {
- try
- {
- Process.Start(new ProcessStartInfo
- {
- FileName = Application.ExecutablePath,
- Arguments = argomento,
- Verb = "runas",
- UseShellExecute = true
- });
- }
- catch (Exception ex)
- {
- MessageBox.Show("Azione annullata o errore:\n" + ex.Message);
- }
- }
-
- private void LstPrinters_MouseDown(object sender, MouseEventArgs e)
- {
- if (e.Button == MouseButtons.Right)
- {
- int index = lstPrinters.IndexFromPoint(e.Location);
- if (index != ListBox.NoMatches)
- {
- lstPrinters.SelectedIndex = index;
- }
- }
- }
-
- private void ApriPreferenze()
- {
- if (lstPrinters.SelectedItem == null) return;
- string printerName = lstPrinters.SelectedItem.ToString().Split('[')[0].Trim();
- Process.Start("rundll32.exe", $"printui.dll,PrintUIEntry /e /n \"{printerName}\"");
- }
-
- private void ApriProprietaStampante()
- {
- if (lstPrinters.SelectedItem == null) return;
- string printerName = lstPrinters.SelectedItem.ToString().Split('[')[0].Trim();
- Process.Start("rundll32.exe", $"printui.dll,PrintUIEntry /p /n \"{printerName}\"");
- }
-
- private void ApriCartellaSpooler()
- {
- Process.Start("explorer.exe", "C:\\Windows\\System32\\spool\\PRINTERS");
- }
-
- private void ImpostaComePredefinita()
- {
- if (lstPrinters.SelectedItem == null) return;
- string printerName = lstPrinters.SelectedItem.ToString().Split('[')[0].Trim();
- Process.Start("RUNDLL32.EXE", $"PRINTUI.DLL,PrintUIEntry /y /n \"{printerName}\"");
- }
- }
-}
diff --git a/PrinterManagerNativeTool/Form1.resx b/PrinterManagerNativeTool/Form1.resx
deleted file mode 100644
index 29dcb1b..0000000
--- a/PrinterManagerNativeTool/Form1.resx
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/PrinterManagerNativeTool/MainWindow.xaml b/PrinterManagerNativeTool/MainWindow.xaml
new file mode 100644
index 0000000..3c68bb7
--- /dev/null
+++ b/PrinterManagerNativeTool/MainWindow.xaml
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PrinterManagerNativeTool/MainWindow.xaml.cs b/PrinterManagerNativeTool/MainWindow.xaml.cs
new file mode 100644
index 0000000..0e98f64
--- /dev/null
+++ b/PrinterManagerNativeTool/MainWindow.xaml.cs
@@ -0,0 +1,389 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Input;
+using PrinterManagerNativeTool.Models;
+using PrinterManagerNativeTool.Services;
+
+namespace PrinterManagerNativeTool;
+
+public partial class MainWindow : Window
+{
+ private readonly ObservableCollection _printers = [];
+ private readonly ICollectionView _printerView;
+ private readonly PrinterService _printerService = new();
+ private readonly NetworkDiagnosticsService _networkDiagnostics = new();
+ private readonly SpoolerService _spoolerService = new();
+
+ private NetworkDiagnosticResult? _lastNetworkDiagnostic;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ _printerView = CollectionViewSource.GetDefaultView(_printers);
+ _printerView.Filter = FilterPrinter;
+ PrintersGrid.ItemsSource = _printerView;
+
+ UpdateSearchPlaceholder();
+ NetworkDiagnosticsPanel.Visibility = Visibility.Collapsed;
+ NoNetworkDiagnosticsText.Visibility = Visibility.Collapsed;
+ OpenWebButton.Visibility = Visibility.Collapsed;
+
+ Loaded += async (_, _) => await RefreshPrintersAsync();
+ }
+
+ private PrinterInfo? SelectedPrinter => PrintersGrid.SelectedItem as PrinterInfo;
+
+ private bool FilterPrinter(object item)
+ {
+ if (item is not PrinterInfo printer)
+ return false;
+
+ var query = SearchBox.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(query))
+ return true;
+
+ return Contains(printer.Name, query)
+ || Contains(printer.PortName, query)
+ || Contains(printer.DriverName, query)
+ || Contains(printer.NetworkHost, query);
+ }
+
+ private static bool Contains(string? value, string query) =>
+ value?.Contains(query, StringComparison.CurrentCultureIgnoreCase) == true;
+
+ private async Task RefreshPrintersAsync()
+ {
+ var previousSelection = SelectedPrinter?.Name;
+ RefreshButton.IsEnabled = false;
+ StatusBarText.Text = "Lettura stampanti in corso...";
+
+ try
+ {
+ var printers = await Task.Run(_printerService.GetPrinters);
+
+ _printers.Clear();
+ foreach (var printer in printers)
+ _printers.Add(printer);
+
+ UpdateSummary();
+
+ if (!string.IsNullOrWhiteSpace(previousSelection))
+ {
+ PrintersGrid.SelectedItem = _printers.FirstOrDefault(
+ p => string.Equals(p.Name, previousSelection, StringComparison.OrdinalIgnoreCase));
+ }
+
+ if (PrintersGrid.SelectedItem is null && _printers.Count > 0)
+ PrintersGrid.SelectedIndex = 0;
+
+ if (_printers.Count == 0)
+ UpdateDetails();
+
+ StatusBarText.Text = $"{_printers.Count} stampanti rilevate";
+ RefreshSpoolerStatus();
+ }
+ catch (Exception ex)
+ {
+ SummaryText.Text = "Impossibile leggere le stampanti";
+ StatusBarText.Text = "Errore durante il caricamento";
+ ShowError(ex);
+ }
+ finally
+ {
+ RefreshButton.IsEnabled = true;
+ }
+ }
+
+ private void UpdateSummary()
+ {
+ var ready = _printers.Count(p => string.Equals(
+ p.StatusText, "Pronta", StringComparison.OrdinalIgnoreCase));
+ var notReady = _printers.Count - ready;
+ var jobs = _printers.Sum(p => (long)p.JobCount);
+
+ SummaryText.Text =
+ $"{_printers.Count} stampanti • {ready} pronte • {notReady} non pronte • {jobs} job";
+ }
+
+ private void RefreshSpoolerStatus()
+ {
+ try
+ {
+ var status = _spoolerService.GetStatus();
+ SpoolerStatusText.Text = $"● Spooler: {TranslateServiceStatus(status)}";
+ }
+ catch
+ {
+ SpoolerStatusText.Text = "○ Spooler: stato non disponibile";
+ }
+ }
+
+ private static string TranslateServiceStatus(string status) =>
+ status switch
+ {
+ "Running" => "in esecuzione",
+ "Stopped" => "arrestato",
+ "Paused" => "in pausa",
+ "StartPending" => "avvio in corso",
+ "StopPending" => "arresto in corso",
+ "PausePending" => "sospensione in corso",
+ "ContinuePending" => "ripresa in corso",
+ _ => status
+ };
+
+ private void SearchBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
+ {
+ UpdateSearchPlaceholder();
+ _printerView.Refresh();
+ }
+
+ private void UpdateSearchPlaceholder() =>
+ SearchPlaceholder.Visibility = string.IsNullOrEmpty(SearchBox.Text)
+ ? Visibility.Visible
+ : Visibility.Collapsed;
+
+ private async void RefreshButton_Click(object sender, RoutedEventArgs e) =>
+ await RefreshPrintersAsync();
+
+ private void ToolsButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (ToolsButton.ContextMenu is not { } menu)
+ return;
+
+ menu.PlacementTarget = ToolsButton;
+ menu.Placement = PlacementMode.Bottom;
+ menu.IsOpen = true;
+ }
+
+ private void PrintersGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
+ {
+ _lastNetworkDiagnostic = null;
+ UpdateDetails();
+ }
+
+ private void PrintersGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+ {
+ if (SelectedPrinter is not null)
+ RunPrinterAction(p => _printerService.OpenQueue(p.Name));
+ }
+
+ private void UpdateDetails()
+ {
+ var printer = SelectedPrinter;
+ var selected = printer is not null;
+
+ PrinterActionsPanel.IsEnabled = selected;
+ CopyDiagnosticButton.IsEnabled = selected;
+
+ if (!selected)
+ {
+ SelectedPrinterName.Text = "Seleziona una stampante";
+ SelectedPrinterStatus.Text = string.Empty;
+ DriverValue.Text = PortValue.Text = HostValue.Text = JobsValue.Text = DefaultValue.Text = "—";
+ PingValue.Text = RawValue.Text = "—";
+ NetworkDiagnosticsPanel.Visibility = Visibility.Collapsed;
+ NoNetworkDiagnosticsText.Visibility = Visibility.Collapsed;
+ OpenWebButton.Visibility = Visibility.Collapsed;
+ return;
+ }
+
+ var hasNetworkHost = printer!.IsNetworkPrinter;
+
+ SelectedPrinterName.Text = printer.Name;
+ SelectedPrinterStatus.Text = $"● {printer.StatusText}";
+ DriverValue.Text = string.IsNullOrWhiteSpace(printer.DriverName) ? "Non disponibile" : printer.DriverName;
+ PortValue.Text = string.IsNullOrWhiteSpace(printer.PortName) ? "Non disponibile" : printer.PortName;
+ HostValue.Text = printer.NetworkHost ?? "Non rilevato";
+ JobsValue.Text = printer.JobCount.ToString();
+ DefaultValue.Text = printer.IsDefault ? "Sì" : "No";
+
+ SetDefaultButton.IsEnabled = !printer.IsDefault;
+ OpenWebButton.Visibility = hasNetworkHost ? Visibility.Visible : Visibility.Collapsed;
+ OpenWebButton.IsEnabled = hasNetworkHost;
+
+ NetworkDiagnosticsPanel.Visibility = hasNetworkHost
+ ? Visibility.Visible
+ : Visibility.Collapsed;
+
+ NoNetworkDiagnosticsText.Visibility = hasNetworkHost
+ ? Visibility.Collapsed
+ : Visibility.Visible;
+
+ NoNetworkDiagnosticsText.Text = printer.PortName.StartsWith(
+ "WSD", StringComparison.OrdinalIgnoreCase)
+ ? "Porta WSD: Windows non espone un indirizzo IP diretto utilizzabile da questi test."
+ : "Questa porta non espone un indirizzo di rete utilizzabile per la diagnostica.";
+
+ DiagnoseButton.IsEnabled = hasNetworkHost;
+ PingValue.Text = hasNetworkHost ? "Non eseguito" : "N/D";
+ RawValue.Text = hasNetworkHost ? "Non eseguito" : "N/D";
+ }
+
+ private void OpenQueueButton_Click(object sender, RoutedEventArgs e) =>
+ RunPrinterAction(p => _printerService.OpenQueue(p.Name));
+
+ private void PreferencesButton_Click(object sender, RoutedEventArgs e) =>
+ RunPrinterAction(p => _printerService.OpenPreferences(p.Name));
+
+ private void PropertiesButton_Click(object sender, RoutedEventArgs e) =>
+ RunPrinterAction(p => _printerService.OpenProperties(p.Name));
+
+ private async void SetDefaultButton_Click(object sender, RoutedEventArgs e)
+ {
+ var printer = SelectedPrinter;
+ if (printer is null)
+ return;
+
+ try
+ {
+ _printerService.SetDefault(printer.Name);
+ StatusBarText.Text = $"{printer.Name} impostata come predefinita";
+ await RefreshPrintersAsync();
+ }
+ catch (Exception ex)
+ {
+ ShowError(ex);
+ }
+ }
+
+ private void TestPageButton_Click(object sender, RoutedEventArgs e) =>
+ RunPrinterAction(p => _printerService.PrintTestPage(p.Name));
+
+ private void OpenWebButton_Click(object sender, RoutedEventArgs e) =>
+ RunPrinterAction(_printerService.OpenWebInterface);
+
+ private async void DiagnoseButton_Click(object sender, RoutedEventArgs e)
+ {
+ var printer = SelectedPrinter;
+ if (printer?.NetworkHost is null)
+ return;
+
+ DiagnoseButton.IsEnabled = false;
+ PingValue.Text = "Test in corso...";
+ RawValue.Text = "Test in corso...";
+ StatusBarText.Text = $"Diagnostica {printer.NetworkHost}...";
+
+ try
+ {
+ _lastNetworkDiagnostic = await _networkDiagnostics.DiagnoseAsync(printer.NetworkHost);
+ PingValue.Text = _lastNetworkDiagnostic.Ping;
+ RawValue.Text = _lastNetworkDiagnostic.Raw9100;
+ StatusBarText.Text = "Diagnostica completata";
+ }
+ catch (Exception ex)
+ {
+ StatusBarText.Text = "Diagnostica non riuscita";
+ ShowError(ex);
+ }
+ finally
+ {
+ DiagnoseButton.IsEnabled = true;
+ }
+ }
+
+ private void CopyDiagnosticButton_Click(object sender, RoutedEventArgs e)
+ {
+ var printer = SelectedPrinter;
+ if (printer is null)
+ return;
+
+ try
+ {
+ Clipboard.SetText(BuildDiagnosticReport(printer));
+ StatusBarText.Text = "Report diagnostico copiato negli Appunti";
+ }
+ catch (Exception ex)
+ {
+ ShowError(ex);
+ }
+ }
+
+ private string BuildDiagnosticReport(PrinterInfo printer)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("Printer Manager Diagnostic Report");
+ sb.AppendLine($"Generated: {DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss zzz}");
+ sb.AppendLine($"Computer: {Environment.MachineName}");
+ sb.AppendLine($"Windows: {Environment.OSVersion.VersionString}");
+ sb.AppendLine();
+ sb.AppendLine($"Printer: {printer.Name}");
+ sb.AppendLine($"Status: {printer.StatusText}");
+ sb.AppendLine($"Default: {(printer.IsDefault ? "Yes" : "No")}");
+ sb.AppendLine($"Driver: {printer.DriverName}");
+ sb.AppendLine($"Port: {printer.PortName}");
+ sb.AppendLine($"Host: {printer.NetworkHost ?? "N/A"}");
+ sb.AppendLine($"Jobs: {printer.JobCount}");
+ sb.AppendLine($"Spooler: {SafeSpoolerStatus()}");
+
+ if (_lastNetworkDiagnostic is not null && printer.IsNetworkPrinter)
+ {
+ sb.AppendLine($"Ping: {_lastNetworkDiagnostic.Ping}");
+ sb.AppendLine($"RAW 9100: {_lastNetworkDiagnostic.Raw9100}");
+ }
+
+ return sb.ToString();
+ }
+
+ private string SafeSpoolerStatus()
+ {
+ try
+ {
+ return _spoolerService.GetStatus();
+ }
+ catch
+ {
+ return "Unavailable";
+ }
+ }
+
+ private void RestartSpoolerMenuItem_Click(object sender, RoutedEventArgs e) =>
+ RunElevatedSpoolerAction("restart-spooler");
+
+ private void ClearSpoolerMenuItem_Click(object sender, RoutedEventArgs e) =>
+ RunElevatedSpoolerAction("clear-spooler");
+
+ private void RunElevatedSpoolerAction(string action)
+ {
+ try
+ {
+ SpoolerService.RunElevated(action);
+ StatusBarText.Text = "Operazione spooler avviata con privilegi amministrativi";
+ }
+ catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
+ {
+ StatusBarText.Text = "Operazione annullata";
+ }
+ catch (Exception ex)
+ {
+ ShowError(ex);
+ }
+ }
+
+ private void RunPrinterAction(Action action)
+ {
+ var printer = SelectedPrinter;
+ if (printer is null)
+ return;
+
+ try
+ {
+ action(printer);
+ }
+ catch (Exception ex)
+ {
+ ShowError(ex);
+ }
+ }
+
+ private static void ShowError(Exception ex) =>
+ MessageBox.Show(
+ ex.Message,
+ "Printer Manager - Errore",
+ MessageBoxButton.OK,
+ MessageBoxImage.Error);
+}
diff --git a/PrinterManagerNativeTool/Models/PrinterInfo.cs b/PrinterManagerNativeTool/Models/PrinterInfo.cs
new file mode 100644
index 0000000..2754f36
--- /dev/null
+++ b/PrinterManagerNativeTool/Models/PrinterInfo.cs
@@ -0,0 +1,48 @@
+namespace PrinterManagerNativeTool.Models;
+
+public sealed class PrinterInfo
+{
+ public string Name { get; init; } = string.Empty;
+ public string PortName { get; init; } = string.Empty;
+ public string DriverName { get; init; } = string.Empty;
+ public string? ServerName { get; init; }
+ public string? ShareName { get; init; }
+ public string? Location { get; init; }
+ public string? Comment { get; init; }
+ public uint StatusFlags { get; init; }
+ public uint Attributes { get; init; }
+ public uint JobCount { get; init; }
+ public bool IsDefault { get; init; }
+ public string? NetworkHost { get; init; }
+
+ public string StatusText => PrinterStatusFormatter.ToDisplayText(StatusFlags);
+ public string DefaultText => IsDefault ? "★" : string.Empty;
+ public bool IsNetworkPrinter => !string.IsNullOrWhiteSpace(NetworkHost);
+}
+
+internal static class PrinterStatusFormatter
+{
+ private const uint Paused = 0x00000001;
+ private const uint Error = 0x00000002;
+ private const uint PaperJam = 0x00000008;
+ private const uint PaperOut = 0x00000010;
+ private const uint Offline = 0x00000080;
+ private const uint Busy = 0x00000200;
+ private const uint Printing = 0x00000400;
+ private const uint UserIntervention = 0x00100000;
+ private const uint DoorOpen = 0x00400000;
+
+ public static string ToDisplayText(uint status)
+ {
+ if ((status & Offline) != 0) return "Offline";
+ if ((status & PaperJam) != 0) return "Carta inceppata";
+ if ((status & PaperOut) != 0) return "Carta esaurita";
+ if ((status & DoorOpen) != 0) return "Sportello aperto";
+ if ((status & UserIntervention) != 0) return "Intervento richiesto";
+ if ((status & Error) != 0) return "Errore";
+ if ((status & Paused) != 0) return "In pausa";
+ if ((status & Printing) != 0) return "Stampa";
+ if ((status & Busy) != 0) return "Occupata";
+ return "Pronta";
+ }
+}
diff --git a/PrinterManagerNativeTool/PrinterHelper.cs b/PrinterManagerNativeTool/PrinterHelper.cs
deleted file mode 100644
index 91d3939..0000000
--- a/PrinterManagerNativeTool/PrinterHelper.cs
+++ /dev/null
@@ -1,210 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using System.Management;
-
-namespace PrinterManagerNativeTool
-{
- public class PrinterInfo
- {
- public string PrinterName { get; set; } = string.Empty;
- public string PortName { get; set; } = string.Empty;
- public string DriverName { get; set; } = string.Empty;
- public uint RawStatus { get; set; }
- public bool IsOffline { get; set; }
- public int Availability { get; set; }
- }
-
- public static class PrinterHelper
- {
- public static List EnumPrintersMultiThread()
- {
- var basicList = EnumAllPrinters();
- var result = new ConcurrentBag();
-
- Parallel.ForEach(basicList, (printerStruct) =>
- {
- var details = GetPrinterDetails(printerStruct.pPrinterName);
- result.Add(details);
- });
-
- // 🔄 Secondo passaggio: aggiorna Availability e WorkOffline via WMI
- try
- {
- using var searcher = new ManagementObjectSearcher("SELECT Name, Availability, WorkOffline FROM Win32_Printer");
- foreach (ManagementObject printer in searcher.Get())
- {
- string name = printer["Name"]?.ToString() ?? "";
- var match = result.FirstOrDefault(p => p.PrinterName.Equals(name, StringComparison.OrdinalIgnoreCase));
- if (match != null)
- {
- if (printer["Availability"] != null)
- match.Availability = Convert.ToInt32(printer["Availability"]);
-
- if (printer["WorkOffline"] != null && Convert.ToBoolean(printer["WorkOffline"]))
- match.IsOffline = true;
- }
- }
- }
- catch
- {
- // Silenzia eventuali errori WMI
- }
-
- return new List(result);
- }
-
-
- private static List EnumAllPrinters()
- {
- var printers = new List();
-
- uint flags = (uint)(PrinterEnumFlags.PRINTER_ENUM_LOCAL | PrinterEnumFlags.PRINTER_ENUM_CONNECTIONS);
- uint level = 2;
- uint pcbNeeded = 0;
- uint pcReturned = 0;
-
- PrinterNative.EnumPrinters(flags, null, level, IntPtr.Zero, 0, out pcbNeeded, out pcReturned);
-
- if (pcbNeeded == 0) return printers;
-
- IntPtr pAddr = Marshal.AllocHGlobal((int)pcbNeeded);
- try
- {
- bool ok = PrinterNative.EnumPrinters(flags, null, level, pAddr, pcbNeeded, out pcbNeeded, out pcReturned);
- if (!ok) return printers;
-
- int sizeStruct = Marshal.SizeOf(typeof(PRINTER_INFO_2));
- IntPtr currentPtr = pAddr;
-
- for (int i = 0; i < pcReturned; i++)
- {
- PRINTER_INFO_2 info = (PRINTER_INFO_2)Marshal.PtrToStructure(currentPtr, typeof(PRINTER_INFO_2))!;
- printers.Add(info);
- currentPtr = IntPtr.Add(currentPtr, sizeStruct);
- }
- }
- finally
- {
- Marshal.FreeHGlobal(pAddr);
- }
-
- return printers;
- }
- private static bool PortExists(string portName)
- {
- try
- {
- using var searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_SerialPort");
- foreach (ManagementObject port in searcher.Get())
- {
- string name = port["Name"]?.ToString() ?? "";
- if (name.Contains(portName, StringComparison.OrdinalIgnoreCase))
- return true;
- }
- }
- catch
- {
- // Logga se necessario
- }
-
- return false;
- }
-
- private static PrinterInfo GetPrinterDetails(string printerName)
- {
- var pi = new PrinterInfo
- {
- PrinterName = printerName,
- PortName = string.Empty,
- DriverName = string.Empty,
- RawStatus = 0,
- IsOffline = false
- };
-
- if (!PrinterNative.OpenPrinter(printerName, out IntPtr hPrinter, IntPtr.Zero) || hPrinter == IntPtr.Zero)
- {
- return pi;
- }
-
- try
- {
- uint level = 2;
- PrinterNative.GetPrinter(hPrinter, level, IntPtr.Zero, 0, out uint cbNeeded);
- if (cbNeeded == 0)
- {
- return pi;
- }
-
- IntPtr pAddr = Marshal.AllocHGlobal((int)cbNeeded);
- try
- {
- bool ok = PrinterNative.GetPrinter(hPrinter, level, pAddr, cbNeeded, out _);
- if (!ok) return pi;
-
- var info2 = (PRINTER_INFO_2_DETAILED)Marshal.PtrToStructure(pAddr, typeof(PRINTER_INFO_2_DETAILED))!;
- pi.PortName = info2.pPortName;
- pi.DriverName = info2.pDriverName;
- pi.RawStatus = info2.Status;
- pi.IsOffline =
- (info2.Status & (uint)PrinterStatus.PRINTER_STATUS_OFFLINE) != 0 ||
- (info2.Attributes & 0x400) != 0; // PRINTER_ATTRIBUTE_WORK_OFFLINE
-
- // ✅ Verifica anche se la porta USB è presente
- if (pi.PortName.StartsWith("USB", StringComparison.OrdinalIgnoreCase))
- {
- bool usbPortActive = PortExists(pi.PortName);
- if (!usbPortActive)
- {
- pi.IsOffline = true;
- }
- }
- }
- finally
- {
- Marshal.FreeHGlobal(pAddr);
- }
- }
- finally
- {
- PrinterNative.ClosePrinter(hPrinter);
- }
-
- return pi;
- }
-
- [Flags]
- public enum PrinterStatus : uint
- {
- PRINTER_STATUS_PAUSED = 0x00000001,
- PRINTER_STATUS_ERROR = 0x00000002,
- PRINTER_STATUS_PENDING_DELETION = 0x00000004,
- PRINTER_STATUS_PAPER_JAM = 0x00000008,
- PRINTER_STATUS_PAPER_OUT = 0x00000010,
- PRINTER_STATUS_MANUAL_FEED = 0x00000020,
- PRINTER_STATUS_PAPER_PROBLEM = 0x00000040,
- PRINTER_STATUS_OFFLINE = 0x00000080,
- PRINTER_STATUS_IO_ACTIVE = 0x00000100,
- PRINTER_STATUS_BUSY = 0x00000200,
- PRINTER_STATUS_PRINTING = 0x00000400,
- PRINTER_STATUS_OUTPUT_BIN_FULL = 0x00000800,
- PRINTER_STATUS_NOT_AVAILABLE = 0x00001000,
- PRINTER_STATUS_WAITING = 0x00002000,
- PRINTER_STATUS_PROCESSING = 0x00004000,
- PRINTER_STATUS_INITIALIZING = 0x00008000,
- PRINTER_STATUS_WARMING_UP = 0x00010000,
- PRINTER_STATUS_TONER_LOW = 0x00020000,
- PRINTER_STATUS_NO_TONER = 0x00040000,
- PRINTER_STATUS_PAGE_PUNT = 0x00080000,
- PRINTER_STATUS_USER_INTERVENTION = 0x00100000,
- PRINTER_STATUS_OUT_OF_MEMORY = 0x00200000,
- PRINTER_STATUS_DOOR_OPEN = 0x00400000,
- PRINTER_STATUS_SERVER_UNKNOWN = 0x00800000,
- PRINTER_STATUS_POWER_SAVE = 0x01000000
- }
-
- }
-}
\ No newline at end of file
diff --git a/PrinterManagerNativeTool/PrinterManagerNativeTool.csproj b/PrinterManagerNativeTool/PrinterManagerNativeTool.csproj
index 203964e..69258b1 100644
--- a/PrinterManagerNativeTool/PrinterManagerNativeTool.csproj
+++ b/PrinterManagerNativeTool/PrinterManagerNativeTool.csproj
@@ -1,25 +1,25 @@
-
-
-
- WinExe
- net8.0-windows
- true
-
-
- true
- true
- true
-
-
- false
-
-
- win-x64
- true
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+ WinExe
+ net10.0-windows
+ true
+ enable
+ enable
+
+ PrinterManagerNativeTool
+ PrinterManagerNativeTool
+
+ win-x64
+ true
+ true
+ true
+ true
+ false
+
+
+
+
+
+
+
diff --git a/PrinterManagerNativeTool/PrinterManagerNativeTool.csproj.user b/PrinterManagerNativeTool/PrinterManagerNativeTool.csproj.user
deleted file mode 100644
index b020096..0000000
--- a/PrinterManagerNativeTool/PrinterManagerNativeTool.csproj.user
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- Form
-
-
-
\ No newline at end of file
diff --git a/PrinterManagerNativeTool/PrinterNative.cs b/PrinterManagerNativeTool/PrinterNative.cs
index 585ed0c..d7b38f8 100644
--- a/PrinterManagerNativeTool/PrinterNative.cs
+++ b/PrinterManagerNativeTool/PrinterNative.cs
@@ -1,103 +1,61 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace PrinterManagerNativeTool
-{
- [Flags]
- public enum PrinterEnumFlags : uint
- {
- PRINTER_ENUM_LOCAL = 0x00000002,
- PRINTER_ENUM_CONNECTIONS = 0x00000004
- // Puoi aggiungere altri flag se vuoi enumerare stampanti di rete remote, ecc.
- }
-
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
- public struct PRINTER_INFO_2
- {
- public string pServerName;
- public string pPrinterName;
- public string pShareName;
- public string pPortName;
- public string pDriverName;
- public string pComment;
- public string pLocation;
- public IntPtr pDevMode;
- public string pSepFile;
- public string pPrintProcessor;
- public string pDatatype;
- public string pParameters;
- public IntPtr pSecurityDescriptor;
- public uint Attributes;
- public uint Priority;
- public uint DefaultPriority;
- public uint StartTime;
- public uint UntilTime;
- public uint Status;
- public uint cJobs;
- public uint AveragePPM;
- }
-
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
- public struct PRINTER_INFO_2_DETAILED
- {
- public string pServerName;
- public string pPrinterName;
- public string pShareName;
- public string pPortName;
- public string pDriverName;
- public string pComment;
- public string pLocation;
- public IntPtr pDevMode;
- public string pSepFile;
- public string pPrintProcessor;
- public string pDatatype;
- public string pParameters;
- public IntPtr pSecurityDescriptor;
- public uint Attributes;
- public uint Priority;
- public uint DefaultPriority;
- public uint StartTime;
- public uint UntilTime;
- public uint Status;
- public uint cJobs;
- public uint AveragePPM;
- }
-
- public static class PrinterNative
- {
- // EnumPrinters
- [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern bool EnumPrinters(
- uint Flags,
- string Name,
- uint Level,
- IntPtr pPrinterEnum,
- uint cbBuf,
- out uint pcbNeeded,
- out uint pcReturned
- );
-
- // GetPrinter
- [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern bool GetPrinter(
- IntPtr hPrinter,
- uint Level,
- IntPtr pPrinter,
- uint cbBuf,
- out uint pcbNeeded
- );
-
- // OpenPrinter / ClosePrinter
- [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern bool OpenPrinter(
- string pPrinterName,
- out IntPtr phPrinter,
- IntPtr pDefault
- );
-
- [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern bool ClosePrinter(
- IntPtr hPrinter
- );
- }
-}
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace PrinterManagerNativeTool.Native;
+
+[Flags]
+internal enum PrinterEnumFlags : uint
+{
+ Local = 0x00000002,
+ Connections = 0x00000004
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct PrinterInfo2Native
+{
+ public IntPtr pServerName;
+ public IntPtr pPrinterName;
+ public IntPtr pShareName;
+ public IntPtr pPortName;
+ public IntPtr pDriverName;
+ public IntPtr pComment;
+ public IntPtr pLocation;
+ public IntPtr pDevMode;
+ public IntPtr pSepFile;
+ public IntPtr pPrintProcessor;
+ public IntPtr pDatatype;
+ public IntPtr pParameters;
+ public IntPtr pSecurityDescriptor;
+ public uint Attributes;
+ public uint Priority;
+ public uint DefaultPriority;
+ public uint StartTime;
+ public uint UntilTime;
+ public uint Status;
+ public uint cJobs;
+ public uint AveragePPM;
+}
+
+internal static class PrinterNative
+{
+ [DllImport("winspool.drv", EntryPoint = "EnumPrintersW", CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool EnumPrinters(
+ PrinterEnumFlags flags,
+ string? name,
+ uint level,
+ IntPtr printerEnum,
+ uint bufferSize,
+ out uint bytesNeeded,
+ out uint returned);
+
+ [DllImport("winspool.drv", EntryPoint = "GetDefaultPrinterW", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool GetDefaultPrinter(StringBuilder? buffer, ref uint bufferChars);
+
+ [DllImport("winspool.drv", EntryPoint = "SetDefaultPrinterW", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool SetDefaultPrinter(string printerName);
+
+ internal static string PtrToString(IntPtr value) =>
+ value == IntPtr.Zero ? string.Empty : Marshal.PtrToStringUni(value) ?? string.Empty;
+}
diff --git a/PrinterManagerNativeTool/Program.cs b/PrinterManagerNativeTool/Program.cs
deleted file mode 100644
index b082fb5..0000000
--- a/PrinterManagerNativeTool/Program.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Windows.Forms;
-
-namespace PrinterManagerNativeTool
-{
- internal static class Program
- {
- [STAThread]
- static void Main(string[] args)
- {
- ApplicationConfiguration.Initialize();
-
- if (args.Length > 0)
- {
- switch (args[0])
- {
- case "riavviaSpooler":
- RiavviaSpooler();
- return;
- case "svuotaSpooler":
- SvuotaSpooler();
- return;
- }
- }
-
- Application.Run(new Form1());
- }
-
- static void RiavviaSpooler()
- {
- try
- {
- Process.Start(new ProcessStartInfo("sc", "stop spooler")
- {
- UseShellExecute = false,
- CreateNoWindow = true
- })?.WaitForExit();
-
- Process.Start(new ProcessStartInfo("sc", "start spooler")
- {
- UseShellExecute = false,
- CreateNoWindow = true
- })?.WaitForExit();
-
- MessageBox.Show("Spooler riavviato correttamente.", "OK", MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- catch (Exception ex)
- {
- MessageBox.Show("Errore nel riavvio dello spooler:\n" + ex.Message);
- }
- }
-
- static void SvuotaSpooler()
- {
- try
- {
- string path = @"C:\\Windows\\System32\\spool\\PRINTERS";
- foreach (var file in Directory.GetFiles(path))
- {
- File.Delete(file);
- }
-
- MessageBox.Show("Spooler svuotato con successo.", "OK", MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- catch (Exception ex)
- {
- MessageBox.Show("Errore durante la pulizia dello spooler:\n" + ex.Message);
- }
- }
- }
-}
diff --git a/PrinterManagerNativeTool/Services/NetworkDiagnosticsService.cs b/PrinterManagerNativeTool/Services/NetworkDiagnosticsService.cs
new file mode 100644
index 0000000..68b1476
--- /dev/null
+++ b/PrinterManagerNativeTool/Services/NetworkDiagnosticsService.cs
@@ -0,0 +1,51 @@
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+
+namespace PrinterManagerNativeTool.Services;
+
+public sealed record NetworkDiagnosticResult(
+ string Ping,
+ string Raw9100,
+ long? PingMilliseconds);
+
+public sealed class NetworkDiagnosticsService
+{
+ public async Task DiagnoseAsync(string host, CancellationToken cancellationToken = default)
+ {
+ var pingText = "Non disponibile";
+ long? pingMs = null;
+
+ try
+ {
+ using var ping = new Ping();
+ var reply = await ping.SendPingAsync(host, TimeSpan.FromSeconds(2), Array.Empty(), new PingOptions(), cancellationToken);
+ if (reply.Status == IPStatus.Success)
+ {
+ pingMs = reply.RoundtripTime;
+ pingText = $"OK ({reply.RoundtripTime} ms)";
+ }
+ else
+ {
+ pingText = reply.Status.ToString();
+ }
+ }
+ catch (Exception ex)
+ {
+ pingText = $"Errore: {ex.Message}";
+ }
+
+ var rawText = "Non disponibile";
+ try
+ {
+ using var tcp = new TcpClient();
+ await tcp.ConnectAsync(host, 9100, cancellationToken).AsTask().WaitAsync(TimeSpan.FromSeconds(2), cancellationToken);
+ rawText = "OK";
+ }
+ catch (Exception ex)
+ {
+ rawText = $"Non raggiungibile ({ex.GetType().Name})";
+ }
+
+ return new NetworkDiagnosticResult(pingText, rawText, pingMs);
+ }
+}
diff --git a/PrinterManagerNativeTool/Services/PrinterService.cs b/PrinterManagerNativeTool/Services/PrinterService.cs
new file mode 100644
index 0000000..a27b30b
--- /dev/null
+++ b/PrinterManagerNativeTool/Services/PrinterService.cs
@@ -0,0 +1,165 @@
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Win32;
+using PrinterManagerNativeTool.Models;
+using PrinterManagerNativeTool.Native;
+
+namespace PrinterManagerNativeTool.Services;
+
+public sealed class PrinterService
+{
+ private const uint Level = 2;
+
+ public IReadOnlyList GetPrinters()
+ {
+ var defaultPrinter = GetDefaultPrinterName();
+
+ return EnumeratePrinters(defaultPrinter)
+ .Where(p => !string.IsNullOrWhiteSpace(p.Name))
+ .OrderByDescending(p => p.IsDefault)
+ .ThenBy(p => p.Name, StringComparer.CurrentCultureIgnoreCase)
+ .ToList();
+ }
+
+ public void SetDefault(string printerName)
+ {
+ if (!PrinterNative.SetDefaultPrinter(printerName))
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "Impossibile impostare la stampante predefinita.");
+ }
+
+ public void OpenQueue(string printerName) => RunPrintUi($"/o /n \"{printerName}\"");
+ public void OpenPreferences(string printerName) => RunPrintUi($"/e /n \"{printerName}\"");
+ public void OpenProperties(string printerName) => RunPrintUi($"/p /n \"{printerName}\"");
+ public void PrintTestPage(string printerName) => RunPrintUi($"/k /n \"{printerName}\"");
+
+ public void OpenWebInterface(PrinterInfo printer)
+ {
+ if (string.IsNullOrWhiteSpace(printer.NetworkHost))
+ throw new InvalidOperationException("Non è stato rilevato un indirizzo di rete per questa stampante.");
+
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = $"http://{printer.NetworkHost}",
+ UseShellExecute = true
+ });
+ }
+
+ private static void RunPrintUi(string arguments)
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "rundll32.exe",
+ Arguments = $"printui.dll,PrintUIEntry {arguments}",
+ UseShellExecute = true
+ });
+ }
+
+ private static List EnumeratePrinters(string? defaultPrinter)
+ {
+ var flags = PrinterEnumFlags.Local | PrinterEnumFlags.Connections;
+
+ PrinterNative.EnumPrinters(flags, null, Level, IntPtr.Zero, 0, out var bytesNeeded, out _);
+ if (bytesNeeded == 0)
+ return [];
+
+ var buffer = Marshal.AllocHGlobal(checked((int)bytesNeeded));
+ try
+ {
+ if (!PrinterNative.EnumPrinters(flags, null, Level, buffer, bytesNeeded, out _, out var returned))
+ throw new Win32Exception(Marshal.GetLastWin32Error(), "Impossibile enumerare le stampanti installate.");
+
+ var result = new List(checked((int)returned));
+ var itemSize = Marshal.SizeOf();
+
+ for (var i = 0; i < returned; i++)
+ {
+ var ptr = IntPtr.Add(buffer, checked((int)i * itemSize));
+ var native = Marshal.PtrToStructure(ptr);
+
+ // Important: all strings referenced by PRINTER_INFO_2 point inside
+ // the EnumPrinters buffer. Copy them to managed strings before
+ // freeing the buffer, otherwise subsequent reads can return
+ // corrupted/empty printer names and ports.
+ var name = PrinterNative.PtrToString(native.pPrinterName);
+ var portName = PrinterNative.PtrToString(native.pPortName);
+
+ result.Add(new PrinterInfo
+ {
+ Name = name,
+ PortName = portName,
+ DriverName = PrinterNative.PtrToString(native.pDriverName),
+ ServerName = NullIfEmpty(PrinterNative.PtrToString(native.pServerName)),
+ ShareName = NullIfEmpty(PrinterNative.PtrToString(native.pShareName)),
+ Location = NullIfEmpty(PrinterNative.PtrToString(native.pLocation)),
+ Comment = NullIfEmpty(PrinterNative.PtrToString(native.pComment)),
+ StatusFlags = native.Status,
+ Attributes = native.Attributes,
+ JobCount = native.cJobs,
+ IsDefault = string.Equals(name, defaultPrinter, StringComparison.OrdinalIgnoreCase),
+ NetworkHost = ResolveNetworkHost(portName)
+ });
+ }
+
+ return result;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+
+ private static string? GetDefaultPrinterName()
+ {
+ uint chars = 0;
+ PrinterNative.GetDefaultPrinter(null, ref chars);
+ if (chars == 0)
+ return null;
+
+ var buffer = new StringBuilder(checked((int)chars));
+ return PrinterNative.GetDefaultPrinter(buffer, ref chars) ? buffer.ToString() : null;
+ }
+
+ private static string? ResolveNetworkHost(string portName)
+ {
+ if (string.IsNullOrWhiteSpace(portName))
+ return null;
+
+ try
+ {
+ using var key = Registry.LocalMachine.OpenSubKey(
+ $@"SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\{portName}");
+
+ var ip = key?.GetValue("IPAddress")?.ToString();
+ var host = key?.GetValue("HostName")?.ToString();
+
+ if (!string.IsNullOrWhiteSpace(ip))
+ return ip;
+
+ if (!string.IsNullOrWhiteSpace(host))
+ return host;
+ }
+ catch
+ {
+ // Registry metadata is optional. Fall back to common port naming below.
+ }
+
+ if (IPAddress.TryParse(portName, out _))
+ return portName;
+
+ if (portName.StartsWith("IP_", StringComparison.OrdinalIgnoreCase) ||
+ portName.StartsWith("EP_", StringComparison.OrdinalIgnoreCase))
+ {
+ var candidate = portName[3..];
+ if (IPAddress.TryParse(candidate, out _) || candidate.Contains('.'))
+ return candidate;
+ }
+
+ return null;
+ }
+
+ private static string? NullIfEmpty(string value) =>
+ string.IsNullOrWhiteSpace(value) ? null : value;
+}
diff --git a/PrinterManagerNativeTool/Services/SpoolerService.cs b/PrinterManagerNativeTool/Services/SpoolerService.cs
new file mode 100644
index 0000000..752e0e0
--- /dev/null
+++ b/PrinterManagerNativeTool/Services/SpoolerService.cs
@@ -0,0 +1,93 @@
+using System.Diagnostics;
+using System.IO;
+using System.ServiceProcess;
+
+namespace PrinterManagerNativeTool.Services;
+
+public sealed class SpoolerService
+{
+ private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(25);
+
+ public string GetStatus()
+ {
+ using var service = new ServiceController("Spooler");
+ service.Refresh();
+ return service.Status.ToString();
+ }
+
+ public async Task RestartAsync()
+ {
+ await Task.Run(() =>
+ {
+ using var service = new ServiceController("Spooler");
+ service.Refresh();
+
+ if (service.Status != ServiceControllerStatus.Stopped &&
+ service.Status != ServiceControllerStatus.StopPending)
+ {
+ service.Stop();
+ }
+
+ service.WaitForStatus(ServiceControllerStatus.Stopped, Timeout);
+ service.Start();
+ service.WaitForStatus(ServiceControllerStatus.Running, Timeout);
+ });
+ }
+
+ public async Task ClearAsync()
+ {
+ await Task.Run(() =>
+ {
+ using var service = new ServiceController("Spooler");
+ var shouldRestart = false;
+
+ try
+ {
+ service.Refresh();
+ if (service.Status != ServiceControllerStatus.Stopped)
+ {
+ shouldRestart = true;
+ if (service.Status != ServiceControllerStatus.StopPending)
+ service.Stop();
+
+ service.WaitForStatus(ServiceControllerStatus.Stopped, Timeout);
+ }
+
+ var spoolDirectory = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.Windows),
+ "System32", "spool", "PRINTERS");
+
+ if (!Directory.Exists(spoolDirectory))
+ return;
+
+ foreach (var file in Directory.EnumerateFiles(spoolDirectory))
+ {
+ File.Delete(file);
+ }
+ }
+ finally
+ {
+ service.Refresh();
+ if (shouldRestart && service.Status == ServiceControllerStatus.Stopped)
+ {
+ service.Start();
+ service.WaitForStatus(ServiceControllerStatus.Running, Timeout);
+ }
+ }
+ });
+ }
+
+ public static void RunElevated(string action)
+ {
+ var executable = Environment.ProcessPath
+ ?? throw new InvalidOperationException("Impossibile determinare il percorso dell'eseguibile.");
+
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = executable,
+ Arguments = $"--admin {action}",
+ Verb = "runas",
+ UseShellExecute = true
+ });
+ }
+}
diff --git a/README.md b/README.md
index 1781c6a..9c069d7 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,91 @@
-# Printer Manager native Tool
-
-A lightweight Windows Forms application (C# / .NET) that simulates the classic printer control panel from Windows XP and 7, for quick and efficient management of installed printers on modern systems (Windows 10/11).
-
-## 🎯 Features
-
-- 📜 Displays a list of all installed printers
-- 🖱️ Right-click menu with:
- - Open Print Queue
- - Printer Preferences
- - Set as Default Printer
- - Printer Properties
-- 🔁 Reload printers list
-- 🧹 Spooler Tools (with admin rights prompt only when needed):
- - Restart Print Spooler
- - Clear Spooler Folder
-
-## 💡 Why this tool?
-
-Windows 11's printer settings are often slow and hidden behind multiple menus.
-This tool restores a minimal and fast interface for technicians and system administrators.
-
-## 🔒 UAC Smart Elevation
-
-The app will **only request admin privileges when needed**, using UAC elevation:
-- Restarting the spooler
-- Clearing the spooler folder
-
-No need to run the whole app as administrator.
-
-## 🛠️ Technologies
-
-- Language: C#
-- Framework: .NET 8.0 (WinForms)
-- Visual Studio 2022
-
-## 🚀 How to Build
-
-1. Open the solution in Visual Studio 2022+
-2. Set configuration to **Release**
-3. Build the project
-4. Run the `.exe`
-
-## 📦 Portable Mode
-
-The application is fully portable and does **not require installation**.
-All dependencies are embedded in the `.exe`.
-
-## 📷 Preview
-
-
-
-## 📄 License
-
-MIT License – feel free to use, modify and redistribute.
+# Printer Manager
+
+A fast native Windows toolbox for managing and diagnosing installed printers without navigating through the Windows 11 Settings app.
+
+The v2 branch modernizes the original WinForms utility with WPF, .NET 10 and the native Windows Fluent theme while keeping the application small, portable and focused on technicians.
+
+## Features
+
+- Modern Windows 11-style WPF interface with automatic light/dark theme
+- Fast list of local and connected printers
+- Search by printer name, driver, port or network address
+- Printer status, current jobs, port, driver and default-printer state
+- Open print queue
+- Open printing preferences
+- Open printer properties
+- Set the default printer
+- Print a Windows test page
+- Open the embedded Web UI when a Standard TCP/IP address can be detected
+- Network diagnostics:
+ - ICMP ping
+ - RAW printing port 9100
+- Copy a compact diagnostic report to the clipboard
+- Spooler status
+- Restart the Print Spooler with UAC elevation only when required
+- Safely clear the spool directory by stopping and restarting the service
+- Portable self-contained single-file build
+
+## Architecture
+
+The application deliberately avoids mixing Windows printer APIs with UI code.
+
+- `Models/PrinterInfo.cs` - printer data model and status formatting
+- `Native/PrinterNative.cs` - minimal `winspool.drv` interop
+- `Services/PrinterService.cs` - enumeration and common printer actions
+- `Services/NetworkDiagnosticsService.cs` - TCP/IP diagnostics
+- `Services/SpoolerService.cs` - privileged spooler operations
+- `MainWindow.xaml` - WPF/Fluent user interface
+
+Printer discovery uses the native Windows spooler API instead of using WMI as a second source of truth.
+
+## Requirements
+
+### Development
+
+- Windows 10 or Windows 11
+- .NET 10 SDK
+- Visual Studio 2026+ or another .NET 10-compatible IDE
+
+### Running the published build
+
+No .NET runtime installation is required for the default self-contained build.
+
+## Build
+
+```powershell
+dotnet restore PrinterManagerNativeTool.sln
+dotnet build PrinterManagerNativeTool.sln -c Release
+```
+
+## Portable publish
+
+```powershell
+dotnet publish .\PrinterManagerNativeTool\PrinterManagerNativeTool.csproj `
+ -c Release `
+ -r win-x64 `
+ --self-contained true `
+ -p:PublishSingleFile=true
+```
+
+The generated executable is architecture-specific. Additional targets such as `win-arm64` can be published separately.
+
+## UAC model
+
+Printer Manager normally runs with standard user privileges.
+
+Administrative elevation is requested only for operations that require it:
+
+- Restart Print Spooler
+- Clear spooler files
+
+The elevated process performs the requested operation and then exits.
+
+## Network detection
+
+For Standard TCP/IP printer ports, Printer Manager reads the Windows print monitor configuration to determine the host/IP address. If the address is available, the UI can open the printer's Web interface and test ping and RAW port 9100.
+
+WSD, vendor-specific and virtual ports may not expose a directly usable IP address and are therefore shown without network diagnostics.
+
+## License
+
+MIT.
diff --git a/main_window.png b/main_window.png
deleted file mode 100644
index bba701a..0000000
Binary files a/main_window.png and /dev/null differ