Figuring which printer name corresponds to which device ID
Solution 1
Below is what I finally have been able to come up with.
Please confirm that SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData
is a supported path, and not just happens to be there in the current implementation, subject to future changes.
There's a little problem with structure alignment, for which I've posted a separate question.
public static class UsbPrinterResolver
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SP_DEVICE_INTERFACE_DATA
{
public uint cbSize;
public Guid InterfaceClassGuid;
public uint Flags;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
private struct SP_DEVICE_INTERFACE_DETAIL_DATA // Only used for Marshal.SizeOf. NOT!
{
public uint cbSize;
public char DevicePath;
}
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
private static extern uint CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags);
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern uint CM_Get_Device_ID(uint dnDevInst, string Buffer, uint BufferLen, uint ulFlags);
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
private static extern uint CM_Get_Device_ID_Size(out uint pulLen, uint dnDevInst, uint ulFlags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs([In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid ClassGuid, string Enumerator, IntPtr hwndParent, uint Flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, [In()] ref SP_DEVINFO_DATA DeviceInfoData, [In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, [In()] ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, uint DeviceInterfaceDetailDataSize, out uint RequiredSize, IntPtr DeviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
private const uint DIGCF_PRESENT = 0x00000002U;
private const uint DIGCF_DEVICEINTERFACE = 0x00000010U;
private const int ERROR_INSUFFICIENT_BUFFER = 122;
private const uint CR_SUCCESS = 0;
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const int OPEN_EXISTING = 3;
private static readonly Guid GUID_PRINTER_INSTALL_CLASS = new Guid(0x4d36e979, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
private static readonly Guid GUID_DEVINTERFACE_USBPRINT = new Guid(0x28d78fad, 0x5a12, 0x11D1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
private static string GetPrinterRegistryInstanceID(string PrinterName) {
if (string.IsNullOrEmpty(PrinterName)) throw new ArgumentNullException("PrinterName");
const string key_template = @"SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData";
using (var hk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
string.Format(key_template, PrinterName),
Microsoft.Win32.RegistryKeyPermissionCheck.Default,
System.Security.AccessControl.RegistryRights.QueryValues
)
)
{
if (hk == null) throw new ArgumentOutOfRangeException("PrinterName", "This printer does not have PnP data.");
return (string)hk.GetValue("DeviceInstanceId");
}
}
private static string GetPrinterParentDeviceId(string RegistryInstanceID) {
if (string.IsNullOrEmpty(RegistryInstanceID)) throw new ArgumentNullException("RegistryInstanceID");
IntPtr hdi = SetupDiGetClassDevs(GUID_PRINTER_INSTALL_CLASS, RegistryInstanceID, IntPtr.Zero, DIGCF_PRESENT);
if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();
try
{
SP_DEVINFO_DATA printer_data = new SP_DEVINFO_DATA();
printer_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
if (SetupDiEnumDeviceInfo(hdi, 0, ref printer_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one device in the set
uint cmret = 0;
uint parent_devinst = 0;
cmret = CM_Get_Parent(out parent_devinst, printer_data.DevInst, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
uint parent_device_id_size = 0;
cmret = CM_Get_Device_ID_Size(out parent_device_id_size, parent_devinst, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get size of the device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
parent_device_id_size++; // To include the null character
string parent_device_id = new string('\0', (int)parent_device_id_size);
cmret = CM_Get_Device_ID(parent_devinst, parent_device_id, parent_device_id_size, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
return parent_device_id;
}
finally
{
SetupDiDestroyDeviceInfoList(hdi);
}
}
private static string GetUSBInterfacePath(string SystemDeviceInstanceID) {
if (string.IsNullOrEmpty(SystemDeviceInstanceID)) throw new ArgumentNullException("SystemDeviceInstanceID");
IntPtr hdi = SetupDiGetClassDevs(GUID_DEVINTERFACE_USBPRINT, SystemDeviceInstanceID, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();
try
{
SP_DEVINFO_DATA device_data = new SP_DEVINFO_DATA();
device_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
if (SetupDiEnumDeviceInfo(hdi, 0, ref device_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one device in the set
SP_DEVICE_INTERFACE_DATA interface_data = new SP_DEVICE_INTERFACE_DATA();
interface_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
if (SetupDiEnumDeviceInterfaces(hdi, ref device_data, GUID_DEVINTERFACE_USBPRINT, 0, ref interface_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one interface in the set
// Get required buffer size
uint required_size = 0;
SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, IntPtr.Zero, 0, out required_size, IntPtr.Zero);
int last_error_code = Marshal.GetLastWin32Error();
if (last_error_code != ERROR_INSUFFICIENT_BUFFER) throw new System.ComponentModel.Win32Exception(last_error_code);
IntPtr interface_detail_data = Marshal.AllocCoTaskMem((int)required_size);
try
{
// FIXME, don't know how to calculate the size.
// See https://stackoverflow.com/questions/10728644/properly-declare-sp-device-interface-detail-data-for-pinvoke
switch (IntPtr.Size)
{
case 4:
Marshal.WriteInt32(interface_detail_data, 4 + Marshal.SystemDefaultCharSize);
break;
case 8:
Marshal.WriteInt32(interface_detail_data, 8);
break;
default:
throw new NotSupportedException("Architecture not supported.");
}
if (SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, interface_detail_data, required_size, out required_size, IntPtr.Zero) == 0) throw new System.ComponentModel.Win32Exception();
// TODO: When upgrading to .NET 4, replace that with IntPtr.Add
return Marshal.PtrToStringAuto(new IntPtr(interface_detail_data.ToInt64() + Marshal.OffsetOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA), "DevicePath").ToInt64()));
}
finally
{
Marshal.FreeCoTaskMem(interface_detail_data);
}
}
finally
{
SetupDiDestroyDeviceInfoList(hdi);
}
}
public static string GetUSBPath(string PrinterName) {
return GetUSBInterfacePath(GetPrinterParentDeviceId(GetPrinterRegistryInstanceID(PrinterName)));
}
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenUSBPrinter(string PrinterName) {
return new Microsoft.Win32.SafeHandles.SafeFileHandle(CreateFile(GetUSBPath(PrinterName), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero), true);
}
}
Usage:
using (var sh = UsbPrinterResolver.OpenUSBPrinter("Zebra Large"))
{
using (var f = new System.IO.FileStream(sh, System.IO.FileAccess.ReadWrite))
{
// Read from and write to the stream f
}
}
Solution 2
Try this (Python code):
import _winreg
HKLM = _winreg.HKEY_LOCAL_MACHINE
#------------------------------------------------------------------------------
def getDevicePath(printerName):
key = _winreg.OpenKey(HKLM,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\%s" \
% printerName)
value =_winreg.QueryValueEx(key, "Port")[0]
assert value.startswith("USB"), \
"Port does not start with 'USB': %s" % value
printerPortNumber = int(value.replace(u"USB", u""))
key = _winreg.OpenKey(HKLM,
r"SYSTEM\CurrentControlSet\Control\DeviceClasses" \
r"\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}")
idx = 0
devicePath = None
while True:
try:
subKeyName = _winreg.EnumKey(key, idx)
subKey = _winreg.OpenKey(key, subKeyName)
try:
subSubKey = _winreg.OpenKey(subKey, r"#\Device Parameters")
baseName = _winreg.QueryValueEx(subSubKey, "Base Name")[0]
portNumber = _winreg.QueryValueEx(subSubKey, "Port Number")[0]
if baseName == "USB" and portNumber == printerPortNumber:
devicePath = subKeyName.replace("##?#USB", r"\\?\usb")
break
except WindowsError:
continue
finally:
idx += 1
except WindowsError:
break
return devicePath
Solution 3
Try this ... let me know if this helps ...
static void Main(string[] args)
{
ManagementObjectSearcher s = new ManagementObjectSearcher(@"Select * From Win32_PnPEntity");
foreach (ManagementObject device in s.Get())
{
// Try Name, Caption and/or Description (they seem to be same most of the time).
string Name = (string)device.GetPropertyValue("Name");
// >>>>>>>>>>>>>>>>>>>> Query String ...
if (Name == "O2Micro Integrated MMC/SD controller")
{
/*
class Win32_PnPEntity : CIM_LogicalDevice
{
uint16 Availability;
string Caption;
string ClassGuid;
string CompatibleID[];
uint32 ConfigManagerErrorCode;
boolean ConfigManagerUserConfig;
string CreationClassName;
string Description;
string DeviceID;
boolean ErrorCleared;
string ErrorDescription;
string HardwareID[];
datetime InstallDate;
uint32 LastErrorCode;
string Manufacturer;
string Name;
string PNPDeviceID;
uint16 PowerManagementCapabilities[];
boolean PowerManagementSupported;
string Service;
string Status;
uint16 StatusInfo;
string SystemCreationClassName;
string SystemName;
};
*/
try
{
Console.WriteLine("Name : {0}", Name);
Console.WriteLine("DeviceID : {0}", device.GetPropertyValue("DeviceID"));
Console.WriteLine("PNPDeviceID : {0}", device.GetPropertyValue("PNPDeviceID"));
Console.WriteLine("ClassGuid : {0}", device.GetPropertyValue("ClassGuid"));
Console.WriteLine("HardwareID :\n{0}", JoinStrings(device.GetPropertyValue("HardwareID") as string[]));
Console.WriteLine("CompatibleID :\n{0}", JoinStrings(device.GetPropertyValue("CompatibleID") as string[]));
}
catch (Exception e)
{
Console.WriteLine("ERROR: {0}", e.Message);
}
}
}
}
static string JoinStrings(string[] sarray)
{
StringBuilder b = new StringBuilder();
if (sarray != null)
{
foreach (string s in sarray)
b.Append(" '" + s + "'\n");
}
return b.ToString();
}
Don't have a USB printer to test against, but this provides the information you are looking for (including for USB devices)...
Description : O2Micro Integrated MMC/SD controller
DeviceID : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
PNPDeviceID : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
ClassGuid : {4d36e97b-e325-11ce-bfc1-08002be10318}
HardwareID :
'PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05'
'PCI\VEN_1217&DEV_8221&SUBSYS_04931028'
'PCI\VEN_1217&DEV_8221&CC_080501'
'PCI\VEN_1217&DEV_8221&CC_0805'
CompatibleID : 'PCI\VEN_1217&DEV_8221&REV_05'
'PCI\VEN_1217&DEV_8221'
'PCI\VEN_1217&CC_080501'
'PCI\VEN_1217&CC_0805'
'PCI\VEN_1217'
'PCI\CC_080501'
'PCI\CC_0805'
Also, for a URI, change the '\'s to '#'s in the URI you are intending of building.
so ..
usb\vid_0a5f&pid_0027\46a072900549\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
becomes
usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
====
As GSerg pointed out that Win32_Printer Class helps with the above code, but doesn't provide the device id.
But if I use Win32_Printer class and print out the "PortName" property, that, for the printers I have installed gives be a port/filename that I can use with CreateFile() and open the device.
e.g.:
Name : Microsoft XPS Document Writer
Description :
DeviceID : Microsoft XPS Document Writer
PNPDeviceID :
PortName : XPSPort:
Name : Fax
Description :
DeviceID : Fax
PNPDeviceID :
PortName : SHRFAX:
Here, writing to "XPSPORT:" or "SHRFAX:" sends data to the printer. What does this do for your USB printer?
Solution 4
Use WinObj
from Microsoft to get the specific device name. http://technet.microsoft.com/en-us/sysinternals/bb896657.aspx . This will quickly get you the proper device name to use with CreateFile
to write directly to your USB printer or simply writing directly to a USB printer adapter with old school parallel port output for custom circuitry!
To open the port associated with a specific printer, you may need to use ntcreatefile
. Use the EnumPrinters
function to return a printer_info_2
structure containing the port name for each printer. This port name can then be opened with ntcreatefile
(an NT internal version of CreateFile
) which is explained here: http://msdn.microsoft.com/en-us/library/bb432380(v=vs.85).aspx
Why does this work? There are three namespace levels in windows NT file/device names and the port name retrieved from EnumPrinters
can only be opened with ntcreatefile
because it is only in the NT namespace. There may be an equivalent win32 namespace link for certain devices and roundabout ways to match them with a printer name but this is difficult as others have shown in prior answers.
Check out the Global??
folder in WinObj
tool to show the symbolic links between win32 namespace and NT namespace on your machine. The old school COM1
, COM2
, LPT1
, etc. device names are simply windows NT namespace symbolic links as well. Google "win32 nt namespace" for a more detailed explanation. (Sorry, but as a new user, I can only post 2 hyperlinks.)
Comments
-
aelveborn about 2 years
My goal is to open a printer connected via USB using the
CreateFile
(and then issue someWriteFile
s andReadFile
s).If the printer was an LPT one, I would simply do
CreateFile("LPT1:", ...)
. But for USB printers, there is a special device path that must be passed toCreateFile
in order to open that printer.This device path, as I was able to find, is retrieved via
SetupDiGetClassDevs
->SetupDiEnumDeviceInterfaces
->SetupDiGetDeviceInterfaceDetail
->DevicePath
member and looks like this:\\?\usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
All that is fine, but what I have as the input is the human-readable printer name, as seen in
Devices and Printers
. TheSetupDi*
functions don't seem to use that, they only operate on device instance IDs. So the question is now how to get device instance ID from the printer name one would pass toOpenPrinter
.It's not difficult to observe that the GUID part of the above is the
GUID_DEVINTERFACE_USBPRINT
, and\\?\usb
is fixed, so the only bit I'm really interested in isvid_0a5f&pid_0027#46a072900549#
. This path I can easily look up manually in the printer properties dialog:Go to Devices and Printers
Right-click the printer
Properties
Switch to Hardware tab
Select the printing device, such as ZDesigner LP2844-Z
Properties
Switch to Details tab
Select 'Parent' from the dropdown.But I have no idea how to do that programmatically provided the only thing given is the printer name as seen in the Device and Printers panel.
P.S. 1: I'm not interested in opening the printer with
OpenPrinter
and then usingWritePrinter
/ReadPrinter
. That has been done, works fine, but now the goal is different.P.S. 2: I'll be OK with a simpler way to convert the readable printer name to something that can be passed to
CreateFile
.P.S. 3: This question, to which I have posted an answer, is very related to what I ultimately want to do.
P.S. 4: The other way round is also fine: If it is possible to obtain the readable name from the
SP_DEVINFO_DATA
structure, that will also be the answer, although a less convenient one. -
aelveborn about 12 yearsThis is promising; however, it does not help as of now. The
Name
it gives is not the name of the printer, it is the name of the driver. So again, I have no way of knowing whether that is the correct printer or not. And if I instead queryWin32_Printer
, thenPNPDeviceID
is blank, andDeviceID
is useless (same as the printer name). -
chkdsk about 12 yearsdoes the PortName property help?
-
aelveborn about 12 yearsNo,
PortName
does not help. It isUSB002
, andCreateFile
fails to open that. On contrary, if the port was "LPT1:", it would succeed. If you know how to makeCreateFile
open something likeUSB002
, that would definitely be the answer. -
chkdsk about 12 yearsHave you tried .. \\.\USB002 ?? Also remembered that if the printer is shared then you can directly open the printer by name: "\\machine-name\printer-name".
-
aelveborn about 12 yearsYes, I've tried
\\.\USB002
and\\?\USB002
. The printer is not shared and will not be. -
aelveborn about 12 yearsIt's not about C++, I'm happy with any language (and in fact, I'm using C# for this project), I just picked C++ as the easiest way to start playing with the API and structures without having a need to declare PInvoke attributes etc. The driver name is useless because it is same for the identical printers I have installed. If you look at my own answer to this question, I'm already doing something similar to what you linked to, but I'm not sure if reading the registry in this way is supported (as opposed to implementation-dependent).
-
Chibueze Opata about 12 yearsHmm, if you are doing C#, then what is the need for the API calls?
-
aelveborn about 12 yearsHow else are you going to get that information? First translate redistry device ID to parent device ID, then the parent device ID to the usbprint interface path? .NET framework does not seem to provide a way.
-
Chibueze Opata about 12 yearsI'm actually saying you don't even need to use the CreateFile and OpenPrinter api calls to get printer information when there is WMI.
-
aelveborn about 12 yearsI don't need printer information for its own sake. I only need printer information in order to open that printer with
CreateFile
. It is not possible to get away with .NET printing subsystem, because it opens printer in a different way, via a spooler. WMI does not provide the required information, as I have noted in a comment to PrashantGupta's answer. -
Admin almost 12 yearsI tried your sample and it gets to the point of:
SetupDIEnumDeviceInfo(hdi, 0, ref printer_data)
and this returns false. I calledMarshal.GetLastWin32Error()
and my error code is 259. I'm stumped. My printer is plugged into USB, and shows in registry. -
walkingTarget over 11 yearsThe above code should work for Plug and Play installed printers (majority of consumer printers). In my case, the printer wasn't installed via PnP and accordingly the PNPData key did not exist. It should also be noted that hard-linked registry key lookups like this aren't recommended as registry organization is subject to change. However, I haven't yet found an API function(s) to establish a relationship between a Printer Name in Devices and Printers and its actual device.
-
Xdg over 7 yearsProblem is that Printer is printer itself (with same name as driver) and PrintQueue (with name visible in Windows) is another device :(