I'm trying to get the total pages count from all the printers connected to a computer. To do that i try in different ways but nothing give me the expected result:
SNMP: Very useful but impossible use it on a local printer.
Win32_printer: No way to get the total page count of a network printer and more of the local printers not store the page count on it.
printui.dll: total page count not supported.
Now i'm trying a different approach: there is a way to programmatically print the device configuration page (Example: THAT) to a file? you can print it whitout connect the printer to a PC's so it shoud be stored somewhere in some digital format. the only thing i found on internet is the Bidi Communication but i can't found any documentation that can help me... I not need a particular programming language, but if possible i prefere c#,c++ or java.
#Soner Gönül: Ok, so i choose c# just because is the language that i use to write the snmp test, but examples in other languages are appreciated:
SNMP:
SNMP snmp = new SNMP();
// Aprire la connessione verso la stampante
try
{
snmp.Open(IPAddressOfPrinter, CommunityString, Retries, TimeoutInMS);
} catch {
MessageBox.Show("Unable to reach the printer");
}
//Sides Counter
lblSides.Text = snmp.Get(".1.3.6.1.2.1.43.10.2.1.4.1.1").ToString();
rtbLog.Text += "Printer Counter: " + lblSides.Text;
rtbLog.Text += " Sides.\n";
That code give me the number of sides printed by the printer, that is ok, but need that the printer is a network printer, on a USB printer i can't use SNMP.
Win32_Printer:
ConnectionOptions options = new ConnectionOptions();
ManagementScope scope = new ManagementScope(#"\\" + System.Environment.MachineName.ToString() + #"\root\cimv2"); //yeah... i know #"root\cimv2" is enought, i notice only now...
scope.Connect();
ObjectQuery PRNquery = new ObjectQuery("SELECT * FROM Win32_Printer");
ManagementObjectSearcher PRNsearcher = new ManagementObjectSearcher(scope, PRNquery);
ManagementObjectCollection PRNqueryCollection = PRNsearcher.Get();
foreach (ManagementObject m in PRNqueryCollection)
{
MessageBox.Show(m["JobCountSinceLastReset"].Tostring());
//But the result is Zero or random error the 90% of the times.
}
priuntui.dll:
If i launch
rundll32 printui.dll PrintUIEntry /something
or try to export the report with:
public static class PrintTest
{
[DllImport("printui.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern void PrintUIEntryW(IntPtr hwnd,
IntPtr hinst, string lpszCmdLine, int nCmdShow);
public static void Print(string printerName)
{
var printParams = string.Format(#"/f C:\test.dat /Xg /n{0}", printerName);
PrintUIEntryW(IntPtr.Zero, IntPtr.Zero, printParams, 0);
}
}
i not found a page count report, but only generic information.
Related
I'm developing 'Share Monitoring Application' via C# and it's monitoring the sharing activities and I'm using these APIs to implement enumerate shared items/un-sharing shared items.
Api used:
NetShareEnum
NetShareDel
NetShareEnum to enumerate all shared items and NetShareDel to delete shared items (=unshare).
I used SHChangeNotify to remove shared mark and directories working fine. (Delete shared item using NetShareDel is not affected immediately.)
But printer state is not affected by SHChangeNotify. Which mean after deleting shared printer via NetShareDel and call SHChangeNotify with SHCNE_NETUNSHARE and SHCNF_PATHW. Also I used SHCNE_NETUNSHARE and SHCNF_PRINTERW too, but nothing happened.
Shared printer's state mark: http://i.stack.imgur.com/1ZGrI.png
In this picture, you can see the users the right side of check circle and that indicate printer is shared.
But after calling NetShareDel to unshared shared printer and it's succeed, but shared mark is disappear.
Anyone know how to implement this? I'm waiting for your help. :D
Sorry for my bad english.
Have you tried going via WMI?
I haven't used it myself to "unshare" a printer, but I use it alot in an application to edit printers and printer-ports in other ways.
I would think something like this should do the trick.
The Win32_Printer class looks like it has a "shared" property, so I would suggest trying to switch it to false.
https://msdn.microsoft.com/en-us/library/aa394363%28v=vs.85%29.aspx
I haven't tested this code with unsharing, but it is the exact same code I use to change other properties.
//get the printer(s) through wmi query
//prep query
SelectQuery query = new SelectQuery(string.Format("select * from Win32_Printer WHERE Name = '{0}'", "printername"));
//create scope (connect to server)
ManagementScope scope = new ManagementScope("\\\\serverName\\root\\cimv2");
//search for printers
ManagementObjectSearcher search = new ManagementObjectSearcher(scope, query);
//get collection of printers (should be 0 or 1, but it returns a collection regardless because of the query
ManagementObjectCollection printers = search.Get();
//iterate through the 0-1 printers and set Shared to false
foreach (ManagementObject printer in printers)
{
printer.SetPropertyValue("Shared",false);
printer.put();
}
I tried WMI and it works on my computer, but other computers throw an exception. And I think the reason of application throw an exception is the one of required library is missing on the computer.
So I'm looking for the API that can be used instead of the WMI.
Finally I found the GetPrinter and SetPrinter from the MSDN.
And also I found PRINTER_INFO_5 structure. According to MSDN, Attributes field indicate the printer's attribute including printer is shared or not. And this can be checked Attributes field has PRINTER_ATTRIBUTE_SHARED value.
Anyway, this problem can be solved only OpenPrinter, GetPrinter and SetPrinter.
This image shows the before and after calling 'UnsharePrinter' method.
This is the method I made to un-share the shared printer.
(Un-sharing the shared printer can be performed via NetShareDel, but it cannot notify printer is un-shared to the system.)
Boolean UnsharePrinter(String printerName) {
// fill PRINTER_DEFAULTS structure
// and set DesiredAccess to PRINTER_ACCESS_ADMINISTER to
// get rights to call SetPrinter
PRINTER_DEFAULTS pd;
pd.pDatatype = IntPtr.Zero;
pd.pDevMode = IntPtr.Zero;
pd.DesiredAccess = PRINTER_ACCESS_ADMINISTER;
IntPtr pDefaults = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PRINTER_DEFAULTS)));
Marshal.StructureToPtr(pd, pDefaults, true);
IntPtr hPrinter;
// open the printer
if ( !OpenPrinter(printerName, out hPrinter, pDefaults) ) {
Marshal.FreeHGlobal(pDefaults);
return false;
}
// first, call Zero pointer and 0 size to get minimum required space
IntPtr pInfo = IntPtr.Zero;
Int32 pcbNeeded;
GetPrinter(hPrinter, 5, pInfo, 0, out pcbNeeded);
// alloc reqiured space and call GetPrinter
pInfo = Marshal.AllocHGlobal(pcbNeeded);
if ( !GetPrinter(hPrinter, 5, pInfo, pcbNeeded, out pcbNeeded) ) {
Marshal.FreeHGlobal(pInfo);
ClosePrinter(hPrinter);
return false;
}
// pointer to structure
PRINTER_INFO_5 pi5 = (PRINTER_INFO_5) Marshal.PtrToStructure(pInfo, typeof(PRINTER_INFO_5));
Marshal.FreeHGlobal(pInfo);
// if printer is not shared, release the memory and exit
if ( (pi5.Attributes & PRINTER_ATTRIBUTE_SHARED) == 0 ) {
ClosePrinter(hPrinter);
return false;
}
// remove the shared flag
pi5.Attributes &= ~PRINTER_ATTRIBUTE_SHARED;
// alloc pointer and make structure as pointer
pInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PRINTER_INFO_5)));
Marshal.StructureToPtr(pi5, pInfo, true);
// set printer
Boolean r = SetPrinter(hPrinter, 5, pInfo, 0);
Marshal.FreeHGlobal(pInfo);
ClosePrinter(hPrinter);
return r;
}
I need to write some code that would prompt a message "printer is connected" when the printer is plugged into the computer and also prompt a message "printer not connected" when I plug out the printer from computer. I also want to list the printers available through a combobox. How can I do this in C# using Visual Studio?
You should use Winspool.lib
C# Signature :
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
EDIT:
You can also use this
foreach (string printer in System.Drawing.Printing.PrinterSettings.InstalledPrinters)
{
//Add in combo box
}
to immediately get the pop-up that new Printer Found/Disconnected... you must have to run some code in background continuously Windows Service is the best for that.. and using the below code you can get the installed printer so first store the currently installed printer in list and after each 10(or any you want) second get the installed printer again if difference found propmt the message accordingly..
this is the snippet to get the installed printer..
private string[] GetAvailablePrinters()
{
var installedPrinters = new string[PrinterSettings.InstalledPrinters.Count];
PrinterSettings.InstalledPrinters.CopyTo(installedPrinters, 0);
var printers = new List<string>();
var printServers = new List<string>();
var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Printer");
foreach (var printer in searcher.Get())
{
var serverName = #"\\" + printer["SystemName"].ToString().TrimStart('\\');
if (!printServers.Contains(serverName))
printServers.Add(serverName);
}
foreach (var printServer in printServers)
{
var server = new PrintServer(printServer);
try
{
var queues = server.GetPrintQueues();
printers.AddRange(queues.Select(q => q.Name));
}
catch (Exception)
{
// Handle exception correctly
}
}
return printers.ToArray();
}
You might need to add the System.Management, System.Drawing, System.Printing references in you project..
I am using this code to add IP addresses to a nic card:
[DllImport("iphlpapi.dll", SetLastError = true)]
private static extern UInt32 AddIPAddress(UInt32 address, UInt32 ipMask, int ifIndex, out IntPtr nteContext,
out IntPtr nteInstance);
public static UInt32 AddIpAddressToInterface(string ipAddress, string subnetMask, int ifIndex)
{
var ipAdd = System.Net.IPAddress.Parse(ipAddress);
var subNet = System.Net.IPAddress.Parse(subnetMask);
unsafe
{
var nteContext = 0;
var nteInstance = 0;
IntPtr ptrNteContext;
var ptrNteInstance = new IntPtr(nteInstance);
return AddIPAddress((uint)BitConverter.ToInt32(ipAdd.GetAddressBytes(), 0), (uint)BitConverter.ToInt32(subNet.GetAddressBytes(), 0), ifIndex, out ptrNteContext,
out ptrNteInstance);
}
}
It seems to be working, but I noticed if I reboot the machine, the IPs are removed. Also, I can see them if I perform an ipconfig from a command line, but I do not see them in the Advanced TCP/IP settings dialog. So, are the IPS really added or do I need do something else to ensure the IPs are bound to the nic card?
the IPs are, in fact, added, but AddIPAddress is non-persistent:
The IPv4 address added by the AddIPAddress function is not persistent. The IPv4 address exists only as long as the adapter object exists. Restarting the computer destroys the IPv4 address, as does manually resetting the network interface card (NIC). Also, certain PnP events may destroy the address.
To create an IPv4 address that persists, the EnableStatic method of the Win32_NetworkAdapterConfiguration Class in the Windows Management Instrumentation (WMI) controls may be used. The netsh commands can also be used to create a persistent IPv4 address.
Source: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365801%28v=vs.85%29.aspx
You can execute the EnableStatic method using WMI.NET (System.Management Namespace) like:
var q = new ObjectQuery("select * from Win32_NetworkAdapterConfiguration where InterfaceIndex=25");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(q);
foreach (ManagementObject nic in searcher.Get())
{
ManagementBaseObject newIP = nic.GetMethodParameters("EnableStatic");
newIP["IPAddress"] = new string[]{"192.168.0.1"};
newIP["SubnetMask"] = new string[]{"255.255.255.0"};
nic.InvokeMethod("EnableStatic", newIP, null);
}
As noted, AddIPAddress and all of iphlpapi.dll show and control the dynamic configuration, which doesn't get persisted.
You can set the static persisted configuration, which would show up in the TCP/IP settings dialogs, using netsh - run netsh interface ipv4 set /? to see how. It's programmatically accessible via the INetCfg interface, but I think some of it is undocumented.
The WMI interfaces are wrappers which mix stuff from both sources, which is why I recommend against using them (as you've noticed, they won't configure disconnected NICs).
I have created a C# application to rename printers on a Citrix server (Server 2008 R2).
The reason for this is because every time a user logs on the printer gets forwarded to the server and gets a unique name(For example Microsoft XPS Document Writer (from WI_UFivcBY4-wgoYOdlQ) in session 3) and from within some applications thats an issue since the printer is pointed to the name and by that you need to change the printer setting everytime you logon a session.
The program itself works like a charm and the printer gets the names I desire.
However the issue is after that the printers have been renamed Windows does not seem to be able to identify them anymore. For example if I try to change default printer i get an error saying "Error 0x00000709 Double check the printer name and make sure that the printer is connected to the network."
var query = new ManagementObjectSearcher("SELECT * FROM Win32_Printer where name like '%(%'");
ManagementObjectCollection result = query.Get();
foreach (ManagementObject printer in result)
{
string printerName = printer["name"].ToString();
if (printerName.IndexOf('(') > 0)
{
printer.InvokeMethod("RenamePrinter", new object[] { printerName.Substring(0, printerName.IndexOf('(')).Trim() + " " + userName }); //userName is provided as an inputparameter when running the application
}
}
Am I missing anything? Are there anything else i need to do when renaming?
I cant seem to find any info regarding this case at all.
i thing this codeproject is what your looking for. But after some own experiences with the printers in C# i can only say it does not make fun and it can be really frustrating
Code with small modifications:
//Renames the printer
public static void RenamePrinter(string sPrinterName, string newName)
{
ManagementScope oManagementScope = new ManagementScope(ManagementPath.DefaultPath);
oManagementScope.Connect();
SelectQuery oSelectQuery = new SelectQuery();
oSelectQuery.QueryString = #"SELECT * FROM Win32_Printer WHERE Name = '" + sPrinterName.Replace("\\", "\\\\") + "'";
ManagementObjectSearcher oObjectSearcher =
new ManagementObjectSearcher(oManagementScope, oSelectQuery);
ManagementObjectCollection oObjectCollection = oObjectSearcher.Get();
if (oObjectCollection.Count == 0)
return;
foreach (ManagementObject oItem in oObjectCollection)
{
int state = (int)oItem.InvokeMethod("RenamePrinter", new object[] { newName });
switch (state)
{
case 0:
//Success do noting else
return;
case 1:
throw new AccessViolationException("Access Denied");
case 1801:
throw new ArgumentException("Invalid Printer Name");
default:
break;
}
}
}
Still works great in 2022, thank you. Just had to change type
int
to
UInt32
to avoid new Exception:
UInt32 state = (UInt32)oItem.InvokeMethod("RenamePrinter", new object[] { newName });
switch (state)
{...
Is there a way to resolve the mac address off the default gateway using c#?
update im working with
var x = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0].GetIPProperties().GatewayAddresses;
but I feel like I'm missing something.
Something like this should work for you, although you probably want to add more error checking:
[DllImport("iphlpapi.dll", ExactSpelling = true)]
public static extern int SendARP(uint destIP, uint srcIP, byte[] macAddress, ref uint macAddressLength);
public static byte[] GetMacAddress(IPAddress address)
{
byte[] mac = new byte[6];
uint len = (uint)mac.Length;
byte[] addressBytes = address.GetAddressBytes();
uint dest = ((uint)addressBytes[3] << 24)
+ ((uint)addressBytes[2] << 16)
+ ((uint)addressBytes[1] << 8)
+ ((uint)addressBytes[0]);
if (SendARP(dest, 0, mac, ref len) != 0)
{
throw new Exception("The ARP request failed.");
}
return mac;
}
What you really want is to perform an Adress Resolution Protocol (ARP) request.
There are good and not soo good ways to do this.
Use an existing method in the .NET framework (though I doubt it exists)
Write your own ARP request method (probably more work than you're looking for)
Use a managed library (if exists)
Use an unmanaged library (such as iphlpapi.dll as suggested by Kevin)
If you know you only need remote to get the MAC adress of a remote Windows computer within your network you may use Windows Management Instrumentation (WMI)
WMI example:
using System;
using System.Management;
namespace WMIGetMacAdr
{
class Program
{
static void Main(string[] args)
{
ManagementScope scope = new ManagementScope(#"\\localhost"); // TODO: remote computer (Windows WMI enabled computers only!)
//scope.Options = new ConnectionOptions() { Username = ... // use this to log on to another windows computer using a different l/p
scope.Connect();
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_NetworkAdapterConfiguration");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
foreach (ManagementObject obj in searcher.Get())
{
string macadr = obj["MACAddress"] as string;
string[] ips = obj["IPAddress"] as string[];
if (ips != null)
{
foreach (var ip in ips)
{
Console.WriteLine("IP address {0} has MAC address {1}", ip, macadr );
}
}
}
}
}
}
You'll probably need to use P/Invoke and some native Win API functions.
Have a look at this tutorial.