Getting a handle on usbprint.sys

When you plug in a usb printer into a computer running Windows 2000, 2003 or XP the “plug and pray” subsystem loads and attaches the usbprint.sys bus driver to it, in essence taking over the device.

So its Thursday morning at work and I decide that since the Mac OS X version of our software can print directly to USB printers bypassing the operating system, its about time we added that functionality to the Linux and Windows versions.

Start on the Linux version

A quick scan through the /usr/src/linux/drivers/usb/devio.c source file reveals all about the usbdevfs file system and the ioctls required. Browse through the USB spec, a few calls to open() and ioctl(), and urb here, an urb there and I’m all done. Piece of cake.

Start on the Windows version

Shudder… Past experiences with Windows and anything to do with low level access to devices has taught me that I am in for the long haul. Each time I try to do something like this it always take 3 – 4 days or excruciating effort and unbearable pain. Oh well, I strap myself to my trusty Aeron chair and away I go.

Now from the MSDN documentation I manage to surmise that as soon as a USB printer is plugged in, the “plug and pray” subsystem loads up the usbprint.sys device driver and passes control over to it. In turn usbprint.sys creates a device node, initialises it and then sets up a device interface so user space programs can access it using the usual device semantics.

Great I think. I’ll just call CreateFile() to open the device and then use WriteFile() to send data to it. Couldn’t be easier. “clickety clack”, the code just flies from my fingers. CreateFile(name….). hmm.. what’s the name of the device. If it was Linux it would be /dev/usblp0 or something like that. Now what is it for Windows. A scan through the MSDN documentation reveals nothing. A search of google produces lots of hits to people asking the same question, but no answers. “Oh, oh this is going to be bad I think”. If there are no answers out there it means it is a closely guarded Microsoft secret and I am going to have to do some deep digging myself.

A more methodical scan of the MSDN docs and Google at least hints to what calls I need to make. In essence you do the following.

  1. Call SetupDiGetClassDevs(interface_guid, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) to get a list of devices.
  2. Call SetupDiEnumDeviceInterfaces() to get a list of available device interfaces.
  3. Call SetupDiGetDeviceInterfaceDetail() to fetch information for the actual device interface we are going to use.
  4. Use the DevicePath member of the PSP_DEVICE_INTERFACE_DETAIL_DATA structure returned by the SetupDiGetDeviceInterfaceDetail() call as the devicename/filename in the CreateFile() call.
  5. Call WriteFile() repeatedly to send data to the device.
  6. Call CloseHandle() to close the device

So in order to be able to find the name of the device, I need to find the GUID of the interface that usbprint.sys creates when it adds the devicenode to the system. Search… search… search… nothing. Search some more, nothing. Look through the Windows SDK and DDK include files with grep. Still nothing. Oh well up comes trusty old regedit, everything windows does is in the registry, and I start to wade through the myriad of nodes and entries for the GUID. After a few hours of despair I find it. The magic number is 0x28d78fad, 0x5a12, 0x11D1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2. You’d think Microsoft would have included it in one of the .h files (devguid.h or usbprint.h) just like they did the interface GUID for keyboards, mice, hid usb devices, usbcam devices etc. etc. etc.

Oh well at least I can now call open CreateFile and Write to the printer. So here we go:

HANDLE usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
                              NULL, OPEN_EXISTING, 0, NULL);
WriteFile(usbhandle, buf, numbytes, &byteswrtten);

The WriteFile call fails and GetLastError() returns 1, ie. the operation is not supported.

More searching, reading, nothing turns up. It keeps failing. I remember that the DDK includes source code for the local portmonitor so I take a look to see how it calls CreateFile for the local port interface. I modify mine to be the same and try again.

HANDLE usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
                              NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL |
                              FILE_FLAG_SEQUENTIAL_SCAN, NULL);
WriteFile(usbhandle, buf, numbytes, &byteswrtten);

Success, the printer starts outputting my job. What is different above? Well replacing OPEN_EXISTING with OPEN_ALWAYS seems to have forced the driver to start the device so that the WriteFile interface is available. Previously OPEN_EXISTING would not try and create a file so the device would not start. At least that is what I think.

4 days later and I am all done.

So here is a quick snippet of some of my code to help others out there that might have the same problem. You will need both the windows SDK and DDK installed and configured correctly for this code to compile. You will also need to link your executable with the setupapi library.

Below I have included the code as both an attachment and inline. It is not meant to compile out of the box. It is only there as a guide.


/* Code to find the device path for a usbprint.sys controlled
* usb printer and print to it

#include <devioctl.h>
#include <usb.h>
#include <usbiodef.h>
#include <usbioctl.h>
#include <usbprint.h>
#include <setupapi.h>
#include <devguid.h>
#include <wdmguid.h>

/* This define is required so that the GUID_DEVINTERFACE_USBPRINT variable is
 * declared an initialised as a static locally, since windows does not include it
 * in any of its libraries

#define SS_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
static const GUID name \
= { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }

SS_DEFINE_GUID(GUID_DEVINTERFACE_USBPRINT, 0x28d78fad, 0x5a12, 0x11D1, 0xae,
               0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);

void SomeFunctionToWriteToUSB()
  HDEVINFO devs;
  DWORD devcount;
  SP_DEVINFO_DATA devinfo;
  DWORD size;
  GUID intfce;
  HANDLE usbHandle;
  DWORD dataType;

  devs = SetupDiGetClassDevs(&intfce, 0, 0, DIGCF_PRESENT |
  if (devs == INVALID_HANDLE_VALUE) {
  devcount = 0;
  devinterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
  while (SetupDiEnumDeviceInterfaces(devs, 0, &intfce, devcount, &devinterface)) {
    /* The following buffers would normally be malloced to he correct size
     * but here we just declare them as large stack variables
     * to make the code more readable
    char driverkey[2048];
    char interfacename[2048];
    char location[2048];
    char description[2048];

    /* If this is not the device we want, we would normally continue onto the
     * next one or so something like 
     * if (!required_device) continue; would be added here
    size = 0;
    /* See how large a buffer we require for the device interface details */
    SetupDiGetDeviceInterfaceDetail(devs, &devinterface, 0, 0, &size, 0);
    devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
    interface_detail = calloc(1, size);
    if (interface_detail) {
      interface_detail->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
      devinfo.cbSize = sizeof(SP_DEVINFO_DATA);
      if (!SetupDiGetDeviceInterfaceDetail(devs, &devinterface, interface_detail,
                                           size, 0, &devinfo)) {
      /* Make a copy of the device path for later use */
      strcpy(interfacename, interface_detail->DevicePath);
      /* And now fetch some useful registry entries */
      size = sizeof(driverkey);
      driverkey[0] = 0;
      if (!SetupDiGetDeviceRegistryProperty(devs, &devinfo, SPDRP_DRIVER, &dataType,
                                            (LPBYTE)driverkey, size, 0)) {
      size = sizeof(location);
      location[0] = 0;
      if (!SetupDiGetDeviceRegistryProperty(devs, &devinfo,
                                            SPDRP_LOCATION_INFORMATION, &dataType,
                                            (LPBYTE)location, size, 0)) {
      usbHandle = CreateFile(interfacename, GENERIC_WRITE, FILE_SHARE_READ,
      if (usbHandle != INVALID_HANDLE_VALUE) {
    /* Now perform all the writing to the device ie.
     * while (some condition) WriteFile(usbHandle, buf, size, &bytes_written);

Technorati Tags:

127 thoughts on “Getting a handle on usbprint.sys”

  1. Let me share with you my findings:
    About using usbmon.dll: this dll cannot be used in more than one process at a time, so if the spooler is running you cannot use it.
    And about packet sizes, using USB 1.1 all ReadFile calls MUST use a buffer of 64 bytes or a multiple of 64. For USB 2.0, this size is 512. For WriteFile in Windows 2000, maximum block size is 4096.

  2. Hello Everyone,

    I have been trying to get the offline\online status of a parallel printer usind Setupdi api functions. I was able to traverse till “LPTENUM\” reg entry and also get the device instance. But when i try to use CM_GET_DEVNODE_STATUS to get the status of the printer, it returns zero whrther printer is connected or not. Lot of search in DDK and Google didnot help me. Can anyone of you tell me if at all we can use setupdi api functions in case of parallel printer.l

    Thank you

  3. I’m ignorant of the specific needs discussed here, but if the idea is to send data to a printer, around the printer driver, search MSDN for “RawDataToPrinter”.

    The Spooler API explicitly provides this capability: All you need is the printer name.
    To summarize the example code provided at

    OpenPrinter() with printer name,
    WritePrinter() with pointer and arbitrary size,

    This works for printers of any port type , network printers, whatever. We use this in several of our in-house utilities.

    Hope this helps!

  4. I am aware of the OpenPrinter()/ClosePrinter() method of sending raw data to an attached printer. The only drawback with this approach is that you need to create a printer first and windows spools the data to disk causing double handling of the data. If your data files are large, this is not a very efficient approach.

  5. Ah, very true. Efficiency?? Intel and DRAM mfgs frown upon this statement! There’s nothing that lots more MBytes and MIPS won’t fix… 😉

    Great job with this code, BTW: I am using it to try out some vendor IOCTLs, and it works like a charm. Saved me *lots* of time.

  6. Hey guys I know this might sound stupid but I am doing a university dissertation… I have a usb to parallel port adapter which uses the usbprint dll file to communicate.

    i have no idea at all… basicly all I want to do is output a binary number onto the parallel port through this adapter.. can you guys help me at all I am using visual

  7. Hi, Thanks Peter.
    I’m working with Device Simulation Framework of the WDK. I’m simulating a Printer Device, but I’m creating another BULK OUT endpoint besides standard Protocol 2 Endpoints (1 In, 1 OUT). I modified the bulkusb exmple in the WDK and just added GUID for USBPRINT.sys (The one you name in this article, previusly I was using another GUID [0xa5dcbf10, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xe] that enumarates I think all the USB Devices plugged but I have to move through every usb device that is attached, now I only have to move through the USB Printers) and I’m able to write to the Device. The only thingI don’t now how to do, and I think some one in the blog has ask the same, is to select the endpont that i want to write to. Does anyone has some insigth in this matter?

  8. Hi, Manuel Chacon, I saw you’ve got a VB program that send PJL commands and reads the response… Thats exactly what Im looking for ! ! Please send me the code to npyt AT


  9. I Peter stupid question im trying to read data from my usb port can i just use readfile if so some how i cant get it to work any ideas i get a handle on the device but some how i cant get it to read but i can write and it does print, please any ideas thanks jeremy

  10. my computer states that I have no operating usb operating. Have tried to find help through windows and the printer company and can find no help

  11. hi all

    i know the code for print a file in single pc using serial port now the problem is i need a help for to print a file in network using serial port using java

    please any one help for me


  12. Hi

    Does the program works for the usb to parallel converter for communication PC to PC.


  13. Dude I have no idea. I provided the code for people to use as is. Why don’t you try it with the usb to parallel converter and post back to this blog for everyone’s benefit.

  14. Peter,
    I just want to say thank you for working so hard and leaving this code available for other developers. It works great.

  15. Hi, Manuel Chacon, I saw you’ve got a VB program that send PJL commands and reads the response… Thats exactly what Im looking for ! ! Please send me the code to
    I have spent a week using Peter’s C code and trying to translate it into vb6


  16. I believe that it is impossible to talk to the Parallel port when it has a driver attached to it. Try doing a search for IOOCX. this is a freeware(i believe) parallel port driver

  17. As i am new bee in device and human interactive world.

    its nice to find such people to share their code to the wold for free.

    i ahev to write one device driver for USB drive with own protocol. can any one suggest me where to start.


  18. Could you give me an example of a typical contents of “interfacename” that is passed to CreateFile (line90). (Just to verify my efforts.) Many thanks!

  19. Controlling STROBE, AUTOFEED, INIT, and SELECTIN is not possible using the USB printer class protocol (such signals do not even exist in that specification). Therefore, there is no documented way to do so on the PL-2305 or similar chips. However, querying ERROR, SELECT, and PE is possible using PL-2305, and therefore should be possible using usbprint.sys. ACK and BUSY are not known to USB printer specification, and if you can query these, you are lucky.
    See my web site for a PL-2305 replacement that does allow controlling all the lines – even without the hassle of getting the handle to usbprint.sys.

  20. Hi. I have a code in a DLL that can open a direct communication line to a USB printer. I can give anyone that code for free. But, I have a serious problem: It seems that going in a tight loop calling WriteFile, overruns the printer. After some time it simply stops, and the communication fails. Any ideas?

  21. Hi Peter,

    Your code serves well as a blueprint and guideline. At least I gained extremely high confidence seeing so many people having positive result with your code. That gave me the motivation to keep trying with your code (keep trying? allow me to explain later).

    To be honest, I am a totally newbie who needs to implement USB printing. I tried #libusb, libusbdotnet, libusb-win32, tons of other references from Google, Microsoft MSDN, other people’s blogs, forums, anything you could imagine in the world of Internet, I tried. However, I didn’t have success with those solution.

    Until I ran into your blog during endlessly googling for solution, it gave me some light in the end of tunnel. I really appreciate that. However, it isn’t smooth sailing for me either. I am not too sure how other people in the comments ‘got it work like a charm, magic, ….’ That doesn’t work for me from the first place, I really had hard time to get the code work.

    I am totally new to WDK/DDK(in the past). I downloaded WDK for WIndows Vista to my Windows XP [note, WDK for Vista works for XP, according to Microsoft website]. The WDK is a monster download, it is 2.3GB. Setup the right include/lib paths. By the time I thought the code will work like a charm for me, I realize that the code contains some type definition which is unknown to the compiler.

    Then I need to find out which include file has the type definition. After that, I discovered that some of the API takes more argument than provided in the code. Being completely ignorant to the API, I need to do some best guess and googling around to temporarily workaround the compilation error. That added into uncertainty if I introduced some bug into the code and made it not working.

    Then I ran into runtime issue – the code seems to abort somewhere when it is trying to get some USB information. Then I am not sure if the information is really needed for data writing.

    After all those try, try and try, and of course heavily modification to the code, finally it works. And I still don’t know why it will happen, due to different DDK version, compiler version? I don’t know.

    There are many comments, those are good suggestions, but I don’t know which is the more workable one for me – some says it works like magic, some says removing the headers, some says compile without DDK, all made up some complicated to follow.

    At last, I just want to reiterate – [b]your blog and code are really good starting point for me. I wouldn’t be successful without going through your code, although I did a lot changes to your original code to make it work. [/b]I want to thank you for that. On the other hand, I guess a complete project (with the ready-to-compile-and-build source code and makefile) will make your blog more complete.

    I know you are just trying to help. But just that one tiny step from you, will be a big step of help for others struggling to get USB print work. Thanks again for your blog and code.

  22. Hi Peter,
    I am new to the USB programming. I tried the following program with a usb connected mobile phone. But as in the previous discusion the device is not recognised in the device manager. In this situation can u kindly help me how to resolve this.

    while(SetupDiEnumDeviceInterfaces(hInfo, 0, (struct _GUID *)&USBIODS_GUID, devicecount, &Interface_Info))
    Here the control is not entering inside the while loop.
    The PC does not have any drivers for mobile. In this condition , what changes i need to do for establishing the communication. Kindly help as this is a critical issue i am facing.
    Thankyou in advance

  23. Hi,
    I am new to Windows driver programming.I am trying to solve a problem we are facing. We have a tool which works with Parallel Port ( LPT1/2/3) to be precise.
    Our newer Desktops does not have LPT ports.Hence we thought of using USB to Parallel port cable, but they dont seem to work.
    We want to add a ECP port (Dummy One say LPT1) and PIPE the USB Driver so that the data flows out of USB ?
    Is it possible ? Is there any soultions for my problem ?

  24. Hello,

    Does anybody has this code working under .NET ? Is it possible to send some source code to start this USB stuff under .NET ?

    I need to get the real-time status of an USB connected HP Laserjet to see if it is Out-Of-Paper. Reading about the GET_PORT_STATUS from the USBPrint specifications this would do the job.

    Do you guys think it is possible ?

    Tried to compile the original Code under VS2008 (VC++ Project) but I get lot’s of errors. I guess I first need to install the latest SDK’s and DDK’s for the missing include files.

    Any help is appriciated !


  25. Does anyone have this code for VB.NET 2005?

    I can’t find anything in the registry about GUIDs or the number you found either…

  26. First, thank the blog owner for show us some of his great pieces!

    To Dmitri,
    Most printer don’t required reverse data transfer, thus readfile can fail to get output from printer.
    Most printer comunicated with printer using ieee-1284 device ID ( IOCTL_USBPRINT_GET_1284_ID) data pipe only. Bulk transfer from printer to PC is not nesasary.

    To Brandon,
    In VB6, I do in this ways.


    Public Type guid: Data(3) As Long: End Type ‘A GUID is 16 bytes long

    Public Sub usbPrintGuid()
    End Sub

    PnPHandle& = SetupDiGetClassDevs(useGuid.Data(0), 0, 0, &H12)
    If (PnPHandle& = -1) Then ErrorExit (“Could not attach to PnP node”)

    Do While SetupDiEnumDeviceInterfaces(PnPHandle&, 0, useGuid.Data(0), HidEntry&, DeviceInterfaceData.cbsize)

  27. I am an EE and have been trying to get a VB Net application that sends PJL commands to a USB inkjet printer (always the default printer = USB001 or aka Printer 0 ) and will also be able to receive replies (data) back from the printer. The commands are a min of 12 bytes in length and they always start with ESC which is char(27). The most helpful info I have found is this effort is by Peter (this site) and also at
    and many other sites.

    I have learned more than I desired about USB API functions and various USB protocols and yet cannot get the application to work. I am using the latest VB .NET
    PJL commands cannot go though spooler or printer driver.

    If anyone wishes to send me code I can test out (I see others have accomplished this task from the above posts) and if it works I will send you a brand new color inkjet printer that is an AIO (All in One = scan, copy, print, fax, etc) and yes it has wireless 802.11 – $250.00 value and I will pay for postage. And include free ink cartridges!


    – Aub

  28. Hello,

    Can you help me please, i am having problem connecting my printer to my PC. I just bought HP photosmart c4180, and yes it comes with a CD, since the CD first need to be installed according to the instructions given, then the usb need to be connected, so i am having problem with installing the CD, when i’m installing the CD, somwhere in the middle of installation process, it says the “usbprinter.sys” is need and could not be found, which means i’m missing this driver and i don’t know how to fix or where to download this driver or whatever it is. My operating system is windows XP 2003, any suggestion?


  29. Hi Peter,

    I’ve just purchased a USB to parallel printer cable. They claim this is IEEE1284 compliant and that it is truly bi-directional. However, it installs the USBPRINT.SYS driver which I believe to be uni-directional?

    Do you happen to know if this is the case and, if so, is there a bi-directional driver?

    Thanks in advance.

  30. Hi,

    I need to read status pins (paper end, …) from usb2lpt convector. I think, this arcicle will not work for me, because usbprint.sys will not be loaded until I connect real printer into convector ? Am I right ?


  31. Not really, it all depends on the usb2lpt connector, If it presents itself as a printer to windows, then the usbprint.sys will just work. If it presents itself as a port and install a driver then this method will never work.

  32. Hello,

    i need some help to get the device id string and some other additional information from the printer. I tried DeviceIoControl with the device handle from createfile and IOCTL_USBPRINT_GET_1284_ID without success.

    So I found this information in the usb documentation.

    Request bRequest
    Get_Device_ID 0
    Get_Port_Status 1
    Soft_Reset 2

    Did anyone know how to send this command to a HP Inkjet printer?

    Thanks in advance

  33. Hi, Its really nice blog with useful information.

    as i am new to programming and i need to have a huge print on Dot matrix printer.
    So i need to have dos like print. can any body help me out.

    i need to send either a dataset or a string directly to print. with custome page setting, coz i need to have print on half of the 80 column continous stationary.

    I will be really welcome a sample application if possible in my mail ID

  34. Woohoo! Finally, a way to print raw codes to a parallel receipt printer WITHOUT having to use a computer with a parallel port!

    Got it to work just fine, with a few tweaks to fit my code.

  35. Great especially the writing part.
    i have one question: is there any way in getting LPT printer’s device id? It seems using file handle returned by CreateFile(“LPT1”,…) way doesn’t work, because DeviceIOCtl always set error code 1 in return.

  36. Peter,

    Great code…I can now write to the printer directly. I’ve tried reading the posts and you said it is possible to read and write . I’m using a EPSON printer that supports remote codes which writes status with a predetermined
    time. I like to read from usbprint.sys can you please offer more information on how it can be done?

    Epson Remote Codes that require bi-directional communication( I was successful on reading the port with opening endports with the driver but I was hoping I can do it with usbprint.sys and not need the extra driver.


  37. Hi,

    great work. I’m also very insterested for version for reading from my Epson printer. Could you please provide some information about that?


  38. Thanks.

    After *weeks* of searching for this, I finally found your blog. Give me Unix any day!
    This is the most promising *strait* forward Windoz usb printing that I’ve found.
    I’ll let you know how it applies to a project for a DOS guy converting (reluctantly) to Doz goes, later.


  39. I hear you. I do all my development on Linux and Mac OS X. Unfortunately sometimes I have to venture into the windows world, most of my customers seem to use this, and I am stuck there for weeks at a time trying to solve problems that take minutes to implement on Linux and Mac OS X.

  40. Thank you for your article!but i have a to set timeouts,before writefile and readfile

  41. I suggest you either read the MSDN documentation on search google. I would imagine that you would need to use an ioctl so set those so something like DeviceIOControl()

  42. Thank you for your article!but i have a to set timeouts,before writefile and readfile,I use overlapped IO instead of non overlapped and set the timeout in the call to WaitForSingleObject.But the question is when WaitForSingleObject returns WAIT_TIMEOUT,I use CancelIo to cancel the job,in Window 2000 xp it’s ok. In sever 2003 it was an error, the cancelio sames no use

  43. Unfortunately I am no Windows expert, I am a Linux / Unix programmer so I could not really tell you why the 2 versions of Windows behave differently, however you have hit on one of the main reasons I hate Windows with a passion, inconsistency.

    Your overlapped I/O idea seems like it should work, I use it often for file I/O and is one of the only part of Windows I find superior to other operating systems.

  44. Thanks for your answer.The question has puzzled me for a few days.I don’t know how to resove it.May be this is not the right way to set timeout

  45. another question : my printer has three pipe , one out two in.I want to read data form pipe 3,so I add PIPE00 to PIPE06 to the pathname,CreateFile returns OK,but the readfile can only read pipe 2

  46. Ok here’s some if my findings. I got a USB2LPT cable from Icidu/Bencent with a Prolific
    PL2305. I wanted to use it as an I/O interface or at least as digital output. With one of
    the USB-libraries i tried i saw the cable only had 1 pipe so reading, which always
    failed anyway, never succeeded and probably never will. With one of my testprograms,
    based on this article, i was able to write to the cable. You can basicly just use
    createfile and writefile using the appropriate device, something like
    ‘\\.\USB#Vid_06a9&Pid_1991#’ and not ‘USB001’ which is probably the host-controller. You
    can look for your cable by searching the registry for ‘USB Printing Support’, it’ll
    probably be in a ControlSet00x or CurrentControlSet key then look in the subkey ‘Device
    parameters’ and check the key ‘Symbolic name’ and that’s what you want to open.

    After a lot of hours getting this thing to output something useful i was pretty sure i
    needed something to create an ACK, pulling the ACK to ground manually made it output my
    data so the PL2305 in my cable really wants a printer or something ‘talking’ to get the
    data going. Connecting the ACK to the strobe and pulling it up or down didn’t work so
    for about $2 i got me a NE555 timer ic and now use it as a pulse (ACK) generator… and
    now i can use the cable as a digital output which was my plan. (yes!) I also tied the
    busy to ground just to be sure the PL2305 doesn’t think the ‘printer’ is busy.

Leave a Reply

Your email address will not be published. Required fields are marked *