From 91afb6175113857ae0437bdd8acd74cf7a778afe Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:40:25 +0200 Subject: [PATCH 01/28] Modernize project to WPF and .NET 10 --- .../PrinterManagerNativeTool.csproj | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) 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 + + + + + + + From dfa7b4da260f35d86c143da0d33a664475208457 Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:40:28 +0200 Subject: [PATCH 02/28] Add WPF application shell with system Fluent theme --- PrinterManagerNativeTool/App.xaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 PrinterManagerNativeTool/App.xaml diff --git a/PrinterManagerNativeTool/App.xaml b/PrinterManagerNativeTool/App.xaml new file mode 100644 index 0000000..78fd014 --- /dev/null +++ b/PrinterManagerNativeTool/App.xaml @@ -0,0 +1,16 @@ + + + + + + From 3b427949cec20d0bd565a1c0743be69cc1c5edb0 Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:40:30 +0200 Subject: [PATCH 03/28] Add printer model --- .../Models/PrinterInfo.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 PrinterManagerNativeTool/Models/PrinterInfo.cs diff --git a/PrinterManagerNativeTool/Models/PrinterInfo.cs b/PrinterManagerNativeTool/Models/PrinterInfo.cs new file mode 100644 index 0000000..493badc --- /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 ? "Sì" : 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"; + } +} From 55281fcbe7b53a5dece4c17771a926cde14dc91d Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:41:32 +0200 Subject: [PATCH 04/28] Refactor native printer interop --- PrinterManagerNativeTool/PrinterNative.cs | 164 ++++++++-------------- 1 file changed, 61 insertions(+), 103 deletions(-) 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; +} From 8120079fb38fbcabd81c7cb64d1ed24cc4d94ab1 Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:41:34 +0200 Subject: [PATCH 05/28] Add printer management service --- .../Services/PrinterService.cs | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 PrinterManagerNativeTool/Services/PrinterService.cs diff --git a/PrinterManagerNativeTool/Services/PrinterService.cs b/PrinterManagerNativeTool/Services/PrinterService.cs new file mode 100644 index 0000000..77b22b6 --- /dev/null +++ b/PrinterManagerNativeTool/Services/PrinterService.cs @@ -0,0 +1,156 @@ +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(); + var nativePrinters = EnumerateNativePrinters(); + + return nativePrinters + .Select(p => new PrinterInfo + { + Name = PrinterNative.PtrToString(p.pPrinterName), + PortName = PrinterNative.PtrToString(p.pPortName), + DriverName = PrinterNative.PtrToString(p.pDriverName), + ServerName = NullIfEmpty(PrinterNative.PtrToString(p.pServerName)), + ShareName = NullIfEmpty(PrinterNative.PtrToString(p.pShareName)), + Location = NullIfEmpty(PrinterNative.PtrToString(p.pLocation)), + Comment = NullIfEmpty(PrinterNative.PtrToString(p.pComment)), + StatusFlags = p.Status, + Attributes = p.Attributes, + JobCount = p.cJobs, + IsDefault = string.Equals( + PrinterNative.PtrToString(p.pPrinterName), + defaultPrinter, + StringComparison.OrdinalIgnoreCase), + NetworkHost = ResolveNetworkHost(PrinterNative.PtrToString(p.pPortName)) + }) + .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 EnumerateNativePrinters() + { + 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)); + result.Add(Marshal.PtrToStructure(ptr)); + } + + 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 (portName.StartsWith("IP_", 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; +} From 35f71efd635a42e42d399f0b5897fbd02c9ba1dc Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:41:38 +0200 Subject: [PATCH 06/28] Add network printer diagnostics --- .../Services/NetworkDiagnosticsService.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 PrinterManagerNativeTool/Services/NetworkDiagnosticsService.cs 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); + } +} From 6c67da1f50daaa1188afe34da90a68a3f3f238a0 Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:41:40 +0200 Subject: [PATCH 07/28] Add robust spooler service --- .../Services/SpoolerService.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 PrinterManagerNativeTool/Services/SpoolerService.cs diff --git a/PrinterManagerNativeTool/Services/SpoolerService.cs b/PrinterManagerNativeTool/Services/SpoolerService.cs new file mode 100644 index 0000000..2d5b8b4 --- /dev/null +++ b/PrinterManagerNativeTool/Services/SpoolerService.cs @@ -0,0 +1,92 @@ +using System.Diagnostics; +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 + }); + } +} From e79f3c7e75030f21b7795ff4d2ffeacda496b332 Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:41:42 +0200 Subject: [PATCH 08/28] Add WPF startup and smart UAC actions --- PrinterManagerNativeTool/App.xaml.cs | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 PrinterManagerNativeTool/App.xaml.cs 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); + } + } +} From 0b7f9b48068a995fc5e38e02763578063b3d549b Mon Sep 17 00:00:00 2001 From: lukethehawk <98876950+lukethehawk@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:42:58 +0200 Subject: [PATCH 09/28] Add modern printer management interface --- PrinterManagerNativeTool/MainWindow.xaml | 208 +++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 PrinterManagerNativeTool/MainWindow.xaml diff --git a/PrinterManagerNativeTool/MainWindow.xaml b/PrinterManagerNativeTool/MainWindow.xaml new file mode 100644 index 0000000..ba2b30f --- /dev/null +++ b/PrinterManagerNativeTool/MainWindow.xaml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + - + + CornerRadius="10" + Padding="14"> + + - + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CornerRadius="10" + Padding="20"> + Margin="0,5,0,15" + FontWeight="SemiBold" + Opacity="0.78"/> - + - + @@ -129,68 +201,79 @@ - - + + - - + + - - + + - - + + - - + + + Style="{StaticResource SectionHeaderStyle}"/> -