using System.Management; using System.Runtime.InteropServices; using CloudPrint.Client.Helpers; namespace CloudPrint.Client.Services.Printing; public sealed class WindowsPrintQueueService : IWindowsPrintQueueService { public WindowsPrintQueueJob? FindLatestJob(string printerName, string documentName) { if (string.IsNullOrWhiteSpace(printerName) || string.IsNullOrWhiteSpace(documentName)) { return null; } try { return EnumerateJobs(printerName) .Where(job => job.DocumentName.Contains(documentName, StringComparison.OrdinalIgnoreCase) || documentName.Contains(job.DocumentName, StringComparison.OrdinalIgnoreCase)) .OrderByDescending(job => job.JobId) .FirstOrDefault(); } catch { return null; } } public bool CancelJob(string printerName, int jobId) { if (string.IsNullOrWhiteSpace(printerName) || jobId <= 0) { return false; } if (CancelJobWithWinSpool(printerName, jobId)) { return true; } foreach (var job in QueryJobObjects(printerName)) { var currentJobId = Convert.ToInt32(job["JobId"] ?? 0); if (currentJobId != jobId) { continue; } job.InvokeMethod("Delete", null); return true; } return false; } private static IEnumerable EnumerateJobs(string printerName) { var spoolerJobs = EnumerateJobsWithWinSpool(printerName); if (spoolerJobs.Count > 0) { return spoolerJobs; } return EnumerateJobsWithWmi(printerName); } private static IReadOnlyList EnumerateJobsWithWinSpool(string printerName) { if (!WindowsPrintApi.OpenPrinter(printerName, out var printerHandle, IntPtr.Zero)) { return Array.Empty(); } try { const int maxJobs = 256; _ = WindowsPrintApi.EnumJobs(printerHandle, 0, maxJobs, 1, IntPtr.Zero, 0, out var bytesNeeded, out _); if (bytesNeeded <= 0) { return Array.Empty(); } var buffer = Marshal.AllocHGlobal(bytesNeeded); try { if (!WindowsPrintApi.EnumJobs(printerHandle, 0, maxJobs, 1, buffer, bytesNeeded, out _, out var jobsReturned)) { return Array.Empty(); } var jobs = new List(jobsReturned); var itemSize = Marshal.SizeOf(); for (var index = 0; index < jobsReturned; index++) { var itemPointer = IntPtr.Add(buffer, index * itemSize); var jobInfo = Marshal.PtrToStructure(itemPointer); jobs.Add(new WindowsPrintQueueJob { JobId = jobInfo.JobId, PrinterName = printerName, DocumentName = jobInfo.DocumentName ?? string.Empty, Status = string.IsNullOrWhiteSpace(jobInfo.StatusText) ? $"0x{jobInfo.Status:X}" : jobInfo.StatusText, Position = jobInfo.Position, TotalPages = jobInfo.TotalPages, PagesPrinted = jobInfo.PagesPrinted }); } return jobs; } finally { Marshal.FreeHGlobal(buffer); } } finally { _ = WindowsPrintApi.ClosePrinter(printerHandle); } } private static bool CancelJobWithWinSpool(string printerName, int jobId) { if (!WindowsPrintApi.OpenPrinter(printerName, out var printerHandle, IntPtr.Zero)) { return false; } try { return WindowsPrintApi.SetJob(printerHandle, jobId, 0, IntPtr.Zero, WindowsPrintApi.JobControlDelete) || WindowsPrintApi.SetJob(printerHandle, jobId, 0, IntPtr.Zero, WindowsPrintApi.JobControlCancel); } finally { _ = WindowsPrintApi.ClosePrinter(printerHandle); } } private static IEnumerable EnumerateJobsWithWmi(string printerName) { foreach (var job in QueryJobObjects(printerName)) { yield return new WindowsPrintQueueJob { JobId = Convert.ToInt32(job["JobId"] ?? 0), PrinterName = printerName, DocumentName = Convert.ToString(job["Document"]) ?? string.Empty, Status = Convert.ToString(job["JobStatus"]) ?? string.Empty }; } } private static IEnumerable QueryJobObjects(string printerName) { var escapedName = printerName.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("'", "''", StringComparison.Ordinal); using var searcher = new ManagementObjectSearcher($"SELECT JobId, Document, JobStatus, Name FROM Win32_PrintJob WHERE Name LIKE '{escapedName},%'"); foreach (ManagementObject job in searcher.Get()) { yield return job; } } }