How to get Printer Info in .NET? - c#

In the standard PrintDialog there are four values associated with a selected printer: Status, Type, Where, and Comment.
If I know a printer's name, how can I get these values in C# 2.0?

As dowski suggested, you could use WMI to get printer properties. The following code displays all properties for a given printer name. Among them you will find: PrinterStatus, Comment, Location, DriverName, PortName, etc.
using System.Management;
...
string printerName = "YourPrinterName";
string query = string.Format("SELECT * from Win32_Printer WHERE Name LIKE '%{0}'", printerName);
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
using (ManagementObjectCollection coll = searcher.Get())
{
try
{
foreach (ManagementObject printer in coll)
{
foreach (PropertyData property in printer.Properties)
{
Console.WriteLine(string.Format("{0}: {1}", property.Name, property.Value));
}
}
}
catch (ManagementException ex)
{
Console.WriteLine(ex.Message);
}
}

This should work.
using System.Drawing.Printing;
...
PrinterSettings ps = new PrinterSettings();
ps.PrinterName = "The printer name"; // Load the appropriate printer's setting
After that, the various properties of PrinterSettings can be read.
Note that ps.isValid() can see if the printer actually exists.
Edit: One additional comment. Microsoft recommends you use a PrintDocument and modify its PrinterSettings rather than creating a PrinterSettings directly.

Look at PrinterSettings.InstalledPrinters

Just for reference, here is a list of all the available properties for a printer ManagementObject.
usage: printer.Properties["PropName"].Value

It's been a long time since I've worked in a Windows environment, but I would suggest that you look at using WMI.

Please notice that the article that dowski and Panos was reffering to (MSDN Win32_Printer) can be a little misleading.
I'm referring the first value of most of the arrays. some begins with 1 and some begins with 0.
for example, "ExtendedPrinterStatus" first value in table is 1, therefore, your array should be something like this:
string[] arrExtendedPrinterStatus = {
"","Other", "Unknown", "Idle", "Printing", "Warming Up",
"Stopped Printing", "Offline", "Paused", "Error", "Busy",
"Not Available", "Waiting", "Processing", "Initialization",
"Power Save", "Pending Deletion", "I/O Active", "Manual Feed"
};
and on the other hand, "ErrorState" first value in table is 0, therefore, your array should be something like this:
string[] arrErrorState = {
"Unknown", "Other", "No Error", "Low Paper", "No Paper", "Low Toner",
"No Toner", "Door Open", "Jammed", "Offline", "Service Requested",
"Output Bin Full"
};
BTW,
"PrinterState" is obsolete, but you can use "PrinterStatus".

I know it's an old posting, but nowadays the easier/quicker option is to use the enhanced printing services offered by the WPF framework (usable by non-WPF apps).
http://msdn.microsoft.com/en-us/library/System.Printing(v=vs.110).aspx
An example to retrieve the status of the printer queue and first job..
var queue = new LocalPrintServer().GetPrintQueue("Printer Name");
var queueStatus = queue.QueueStatus;
var jobStatus = queue.GetPrintJobInfoCollection().FirstOrDefault().JobStatus

As an alternative to WMI you can get fast accurate results by tapping in to WinSpool.drv (i.e. Windows API) - you can get all the details on the interfaces, structs & constants from pinvoke.net, or I've put the code together at http://delradiesdev.blogspot.com/2012/02/accessing-printer-status-using-winspool.html

Related

WMI Does not send Printed Job status with C#

I'm using WMI to get Print-Job statuses using WMI with C# code.
now, I'm getting all the different statuses from the printer beside printed which is very important for me to know when the job was finished printing the job.
now, I managed to get printed status but only if the Keep printed Documents (see attachment) property is on. but all the jobs are staying in the queue of the printer and I don't want that.
is there any way to get printed status from the printer without marking the Keep printed Documents property is on
Printer Properties
I'm not sure I understood. You need a timer like DispatcherTimer to set an interval for the request. In the Tick you can check if the status of the printer is changed. Is that what you want?
You can try this piece of code to set the KeepPrintedJobs property to true.
string searchQuery = "SELECT * FROM Win32_Printer";
ManagementObjectSearcher searchPrinters = new
ManagementObjectSearcher(searchQuery);
ManagementObjectCollection printerCollection = searchPrinters.Get();
foreach (ManagementObject printer in printerCollection)
{
PropertyDataCollection printerProperties = printer.Properties;
foreach (PropertyData property in printerProperties)
{
if (property.Name == "KeepPrintedJobs")
{
printerProperties[property.Name].Value = true;
}
}
printer.Put();
}

c# setting a printer

Hello and thank you for you time on this question, I'm trying change a Active Printer according to the choice that user chooses in excel. However I'm having some trouble. For some reason it keeps giving me the same error.
"An exception of type 'System.Runtime.InteropServices.COM Exception'
occurred in DailyReport.dll but was not handled in user code Exception
from HRESULT:0X800A03EC"
I've been goggling this error and im having a hard time finding anything, I did find a link COM Exception and they provided a link to another website but seems when I try to go that site it doesn't open.
I have Tried:
xlApp.ActivePrinter = "CORPPRT58-Copier Room on RR-PS1:";
xlApp.ActivePrinter = "\\RR-PS1\CORPPRT58-Copier Room";
xlApp.ActivePrinter = "CORPPRT58-Copier Room on RR-PS1";
I have checked to make sure that the printer is installed and it is. if someone could point me in the correct direction that would be great thanks!!
The correct answer is (i.e.):
xlApp.ActivePrinter = "\\\\RR-PS1\\CORPPRT58-Copier Room on Ne00:";
An important part is the 'Ne00:' This is the port on which this printer can be found. This is different on each computer and can be found in registry at HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Devices.
Another problem is the concatenation string 'on'. This may be valid when working with an English excel but it's translated to other languages!
I had the same problem but there aren't many complete examples I could find so here is mine:
// Open excel document
var path = #"c:\path\to\my\doc.xlsx";
Microsoft.Office.Interop.Excel.Application _xlApp;
Microsoft.Office.Interop.Excel.Workbook _xlBook;
_xlApp = new Microsoft.Office.Interop.Excel.Application();
_xlBook = _xlApp.Workbooks.Open(path);
_xlBook.Activate();
var printer = #"EPSON LQ-690 ESC/P2";
var port = String.Empty;
// Find correct printerport
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(path))
{
if (key != null)
{
object value = key.GetValue(printer);
if (value != null)
{
string[] values = value.ToString().Split(',');
if (values.Length >= 2) port = values[1];
}
}
}
// Set ActivePrinter if not already set
if (!_xlApp.ActivePrinter.StartsWith(printer))
{
// Get current concatenation string ('on' in enlgish, 'op' in dutch, etc..)
var split = _xlApp.ActivePrinter.Split(' ');
if (split.Length >= 3)
{
_xlApp.ActivePrinter = String.Format("{0} {1} {2}",
printer,
split[split.Length - 2],
port);
}
}
// Print document
_xlBook.PrintOutEx();
It's far for perfect since I'm not aware of any other translations. If 'on' is translated with spaces, above will fail. But i'm guessing that the solution will work for most clients. You can easily get the current concatenation string by looking at the currect value of ActivePrinter.
A more failproof method would be to strip the printer name and the assigned port and what remains is the concatenation string. But then you would have to loop through all installed printers and check for a match.
Another test I personally do it check if the printer is installed on the system:
if(PrinterSettings.InstalledPrinters.Cast<string>().ToList().Contains(printer)) {
//Set active printer...
}
First set your target printer as the default printer on the control pannel. Then print xlApp.ActivePrinter and get its settings(if you don't set the activeprinter the application gets the default setting of windows). At last set the value with the print value. And you can change your default printer setting.

Is there a way to check if a printing process was successful?

I have an application where I need to print a ticket. Each ticket must be unique. The application is windows forms and written entirely in c#. For our application we're using Samsung ML- 2525 laser monochromatic printers.
The flow is basically the following, the operator picks a product/ticket (which is unique) and then it presses a button that does 2 things:
Connects to a database and updates the product as used
Prints the ticket (this is done using System.Drawing and GDI+)
For some reason, every once in a while, the image that needs to be printed is not sent to the printer. It's a rare case, but it happens.
I tried to connect to the printer using Win32_Printer ( http://msdn.microsoft.com/en-us/library/Aa394363 ) but I can't get to get the current printer's state (online, offline, low toner, paper jam, etc). I can only check if the printer exists and that the paper size is installed correctly. I tried code similar to the following but it didn't work
private string MonitorPrintJobWmi()
{
var jobMessage = String.Empty;
var scope = new ManagementScope(ManagementPath.DefaultPath);
scope.Connect();
var selectQuery = new SelectQuery { QueryString = #"select * from Win32_PrintJob" };
var objSearcher = new ManagementObjectSearcher(scope, selectQuery);
var objCollection = objSearcher.Get();
foreach (var job in objCollection)
{
if (job != null)
{
jobMessage += String.Format("{0} \r\n", job["Name"].ToString());
jobMessage += String.Format("{0} \r\n", job["JobId"].ToString());
_jobId = Convert.ToInt32(job["JobId"]);
jobMessage += String.Format("{0} \r\n", job["JobStatus"].ToString());
jobMessage += String.Format("{0} \r\n", job["Status"].ToString());
}
}
return jobMessage;
}
I tried to get an API for the printer but I couldn't get a hold of it. By the way, the printer's software do indicate different errors in the windows toolbar.
My question is if anyone can lead me in the right direction as to how to connect to a printer and check if printing was successful.
Also, it would be helpful if someone know of some other specific printer in which I may accomplish this ie, changing hardware.
Thanks,
To get a list of print queues on the local machine, try PrintServer's GetPrintQueues method.
Once you have an instance of the PrintQueue object associated with the relevant printer, you can use it to access the printer's status (IsOffline, IsPaperOut, etc.). Also, you can use it to get a list of the jobs in the given queue (GetPrintJobInfoCollection) which then will allow you to get job-specific status information (IsInError, IsCompleted, IsBlocked, etc.).
Hope this helps!
After try to print your PrintDocument (System.Drawing.Printing), try to check status of printjobs.
First step: Initialize your printDocument.
Second step: Get your printer Name From System.Drawing.Printing.PrinterSettings.InstalledPrinters.Cast<string>();
And copy it into your printerDocument.PrinterSettings.PrinterName
Third step: Try to print and dispose.
printerDocument.Print();
printerDocument.Dispose();
Last step: Run the check in a Task (do NOT block UI thread).
Task.Run(()=>{
if (!IsPrinterOk(printerDocument.PrinterSettings.PrinterName,checkTimeInMillisec))
{
// failed printing, do something...
}
});
Here is the implementation:
private bool IsPrinterOk(string name,int checkTimeInMillisec)
{
System.Collections.IList value = null;
do
{
//checkTimeInMillisec should be between 2000 and 5000
System.Threading.Thread.Sleep(checkTimeInMillisec);
// or use Timer with Threading.Monitor instead of thread sleep
using (System.Management.ManagementObjectSearcher searcher = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_PrintJob WHERE Name like '%" + name + "%'"))
{
value = null;
if (searcher.Get().Count == 0) // Number of pending document.
return true; // return because we haven't got any pending document.
else
{
foreach (System.Management.ManagementObject printer in searcher.Get())
{
value = printer.Properties.Cast<System.Management.PropertyData>().Where(p => p.Name.Equals("Status")).Select(p => p.Value).ToList();
break;
}
}
}
}
while (value.Contains("Printing") || value.Contains("UNKNOWN") || value.Contains("OK"));
return value.Contains("Error") ? false : true;
}
Good luck.

How to set a Printer Port in C# on a specified Printer

I want to be able to do the equivalent of the following in c#:
Go into "Devices and Printers"
Right click a printer
Go to the ports tab
Select COM12: port
click OK
I have managed to get a ManagementObject for a named printer and tried to call:
mObj.SetPropertyValue("PortName","COM12:");
On the ManagementObject for that Printer - whch inherits from ManagementBaseObject.
However, this piece of code didn't do anything to the Printer Settings.
I suspect that I will need to use P/Invoke on the appropriate library but after a lot of searching I cannot find the library or a tutorial for how to do this from c#.
Any help would be much appreciated.
Found the answer on an obscure part of the internet:
public static void SetPrinterPort(string printerName, string portName)
{
var oManagementScope = new ManagementScope(ManagementPath.DefaultPath);
oManagementScope.Connect();
SelectQuery oSelectQuery = new SelectQuery();
oSelectQuery.QueryString = #"SELECT * FROM Win32_Printer
WHERE Name = '" + printerName.Replace("\\", "\\\\") + "'";
ManagementObjectSearcher oObjectSearcher =
new ManagementObjectSearcher(oManagementScope, #oSelectQuery);
ManagementObjectCollection oObjectCollection = oObjectSearcher.Get();
foreach (ManagementObject oItem in oObjectCollection)
{
oItem.Properties["PortName"].Value = portName;
oItem.Put();
}
}
I am pretty sure that the last part is all you need to set properties on any Management Object. It was driving me nuts that I could see that the Win32 API said the property I wanted to set was read/write but nowhere seemed to have the code for setting it.
Well now we know! :-)
I hope this is useful for someone.

Is there a .NET way to enumerate all available network printers?

Is there a straightforward way to enumerate all visible network printers in .NET? Currently, I'm showing the PrintDialog to allow the user to select a printer. The problem with that is, local printers are displayed as well (along with XPS Document Writer and the like). If I can enumerate network printers myself, I can show a custom dialog with just those printers.
Thanks!!
Get the default printer from LocalPrintServer.DefaultPrintQueue
Get the installed printers (from user's perspective) from PrinterSettings.InstalledPrinters
Enumerate through the list:
Any printer beginning with \\ is a network printer - so get the queue with new PrintServer("\\UNCPATH").GetPrintQueue("QueueName")
Any printer not beginning with \\ is a local printer so get it with LocalPrintServer.GetQueue("Name")
You can see which is default by comparing FullName property.
Note: a network printer can be the default printer from LocalPrintServer.DefaultPrintQueue, but not appear in LocalPrintServer.GetPrintQueues()
// get available printers
LocalPrintServer printServer = new LocalPrintServer();
PrintQueue defaultPrintQueue = printServer.DefaultPrintQueue;
// get all printers installed (from the users perspective)he t
var printerNames = PrinterSettings.InstalledPrinters;
var availablePrinters = printerNames.Cast<string>().Select(printerName =>
{
var match = Regex.Match(printerName, #"(?<machine>\\\\.*?)\\(?<queue>.*)");
PrintQueue queue;
if (match.Success)
{
queue = new PrintServer(match.Groups["machine"].Value).GetPrintQueue(match.Groups["queue"].Value);
}
else
{
queue = printServer.GetPrintQueue(printerName);
}
var capabilities = queue.GetPrintCapabilities();
return new AvailablePrinterInfo()
{
Name = printerName,
Default = queue.FullName == defaultPrintQueue.FullName,
Duplex = capabilities.DuplexingCapability.Contains(Duplexing.TwoSidedLongEdge),
Color = capabilities.OutputColorCapability.Contains(OutputColor.Color)
};
}).ToArray();
DefaultPrinter = AvailablePrinters.SingleOrDefault(x => x.Default);
using the new System.Printing API
using (var printServer = new PrintServer(string.Format(#"\\{0}", PrinterServerName)))
{
foreach (var queue in printServer.GetPrintQueues())
{
if (!queue.IsShared)
{
continue;
}
Debug.WriteLine(queue.Name);
}
}
found this code here
private void btnGetPrinters_Click(object sender, EventArgs e)
{
// Use the ObjectQuery to get the list of configured printers
System.Management.ObjectQuery oquery =
new System.Management.ObjectQuery("SELECT * FROM Win32_Printer");
System.Management.ManagementObjectSearcher mosearcher =
new System.Management.ManagementObjectSearcher(oquery);
System.Management.ManagementObjectCollection moc = mosearcher.Get();
foreach (ManagementObject mo in moc)
{
System.Management.PropertyDataCollection pdc = mo.Properties;
foreach (System.Management.PropertyData pd in pdc)
{
if ((bool)mo["Network"])
{
cmbPrinters.Items.Add(mo[pd.Name]);
}
}
}
}
Update:
"This API function can enumerate all network resources, including servers, workstations, printers, shares, remote directories etc."
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=741&lngWId=10
PrinterSettiings.InstalledPrinters should give you the collection you want
In another post(https://stackoverflow.com/a/30758129/6513653) relationed to this one, Scott Chamberlain said "I do not believe there is anything in .NET that can do this, you will need to make a native call". After to try all the possible .NET resource, I think he is right.
So, I started to investigate how ADD PRINTER dialog does its search. Using Wireshark, I found out that ADD PRINTER send at least two types of packages to all hosts in local network: two http/xml request to 3911 port and three SNMP requests.
The first SNMP request is a get-next 1.3.6.1.2.1.43, which is Printer-MIB. The second one, is a get 1.3.6.1.4.1.2699.1.2.1.2.1.1.3 which is pmPrinterIEEE1284DeviceId of PRINTER-PORT-MONITOR-MIB. This is the most interesting because is where ADD PRINTER takes printer name. The third is a get 1.3.6.1.2.1.1.1.0, which is sysDescr of SNMP MIB-2 System.
I do believe that the second SNMP request is enough to find most of network printers in local network, so I did this code. It works for Windows Form Application and it depends on SnmpSharpNet.
Edit: I'm using ARP Ping instead normal Ping to search active hosts in network. Link for an example project: ListNetworks C# Project
Note that if you're working over RDP it seems to complicate this because it looks like it just exports everything on the host as a local printer.
Which is then a problem if you're expecting it to work the same way when not on RDP.

Categories

Resources