Printing from a Windows Service

It is often desirable to print a document to a printer from your Windows service. While calling the Windows Win32 GDI APIs to send a document to the printer is supported, there are some important considerations to take into account, such as which user account the service is running under, and whether that user has ever logged into the computer before.

Some scenarios would be a Windows service that runs in the background and needs to print reports to a printer, or a Windows service that needs to print errors when an infrequent problem occurs on a server machine.

A Windows service that is designed to print can use the Win32 GDI APIs to print documents to a printer. There are some considerations when printing from Windows services, however. If all the print jobs can be sent with one user's credentials, the service can be run under that user's account and will have access to the printers that are known to that user on that computer. If the service must print using the credentials of one or more users defined at run time, then the service must call LogonUser(), LoadUserProfile(), and ImpersonateLoggedOnUser() before printing. 

While it is possible to send GDI+ output to a printer by obtaining a device context handle for the printer and then passing that handle to a GDI+ Graphics constructor, this is not recommended. The GDI+ functions and classes are not supported for use within a Windows service. Attempting to use these functions and classes from a Windows service may produce unexpected problems, such as diminished service performance and run-time exceptions or errors:

   msdn.microsoft.com/en-us/library/windows/desktop/ms533798(v=VS.85).aspx

In addition, if you are developing a Microsoft .NET application, System.Drawing is not supported in a service or web application. Attempting to use System.Drawing within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions (see the “Caution” section in msdn.microsoft.com/en-us/library/system.drawing.aspx).

System.Printing is also not supported in a service context. This is documented on MSDN here. System.Printing is part of Windows Presentation Foundation.

   msdn.microsoft.com/en-us/library/bb613549(v=vs.90).aspx

You should be using the Win32 GDI APIs when printing from your Windows Service:

  msdn.microsoft.com/en-us/library/windows/desktop/dd145203(v=vs.85).aspx

The following Win32 C++ sample code demonstrates how to print a single page to a printer using the Win32 GDI APIs. The following is only intended as sample code.         

        TCHAR szPrinterName[MAX_PATH] = TEXT("");
        DWORD dwNameLen = MAX_PATH;
        HDC hDC;
        DOCINFO di;

        // We will operate on the default printer
        GetDefaultPrinter( szPrinterName, &dwNameLen );
        // Create the HDC for the printer
        if( (hDC = CreateDC( TEXT("WINSPOOL"), szPrinterName, NULL, NULL )) == NULL )
            return FALSE;

        // The StartDoc function starts the print job
        ZeroMemory( &di, sizeof(DOCINFO) );
        di.cbSize = sizeof(DOCINFO);
        di.lpszDocName = TEXT("Print Job");

        if( StartDoc( hDC, &di ) <= 0 )
        {
            DeleteDC( hDC );
            return FALSE;
        }

        // The StartPage function prepares the printer driver to accept data
        if( StartPage( hDC ) <= 0 )
        {
            EndDoc( hDC );
            DeleteDC( hDC );
            return FALSE;
        }

        TCHAR szString[32] = TEXT("Printing from a service");
        TextOut( hDC, 0, 0, szString, lstrlen( szString ) );

        if( EndPage( hDC ) <= 0 )
        {
            EndDoc( hDC );
            DeleteDC( hDC );
            return FALSE;
        }

        if( EndDoc( hDC ) <= 0 )
        {
            DeleteDC( hDC );
            return FALSE;
        }

        //  Delete the HDC
        DeleteDC( hDC );

 

If you are developing a Microsoft .NET application, you can use Platform Invocation Services (PInvoke) to call the Win32 GDI APIs to print. The following C# sample code demonstrates how to print a rectangle to a printer using the Win32 GDI APIs. The following is only intended as sample code.          

        private void PrintRectangle()
        {
            const int ERROR_INSUFFICIENT_BUFFER = 122;

            IntPtr hdc;
            DOCINFO di = new DOCINFO();
            di.cbSize = 20;
            di.pDocName = "TestingGDI";

            int pcchBuffer = 256;
            StringBuilder pszBuffer = new StringBuilder(pcchBuffer);

            if (GetDefaultPrinter(null, ref pcchBuffer))
            {
                return;
            }

            int lastWin32Error = Marshal.GetLastWin32Error();
            if (lastWin32Error == ERROR_INSUFFICIENT_BUFFER)
            {
                pszBuffer.Capacity = pcchBuffer;
                GetDefaultPrinter(pszBuffer, ref pcchBuffer);
            }

            // This is the default printer
            String szPrinter = pszBuffer.ToString();

            hdc = CreateDC(IntPtr.Zero, szPrinter,
                 IntPtr.Zero, IntPtr.Zero);

            Int32 nRes = StartDoc(hdc, di);

            if (nRes > 0)
            {
                // We call the Win32 StartPage to start a new page on the HDC
                nRes = StartPage(hdc); // You can add error checking to this code

                // Draw a Rectangle
                Rectangle(hdc, 10, 10, 500, 500);

                nRes = EndPage(hdc);

                // First, call the Win32 function to signal the end of the document
                EndDoc(hdc);
                //Then delete the HDC we created for this print job
                DeleteDC(hdc);
            }
        }

        [DllImport("winspool.drv", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool GetDefaultPrinter(StringBuilder pszBuffer, ref int pcchBuffer);

        // Win32 DOCINFO structore for starting a print document
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class DOCINFO
        {
            public int cbSize;
            public string pDocName;
            public string pOutputFile;
            public string pDataType;
            public int fwType;
        }

        // CreateDC() to create a printer HDC
        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr CreateDC(IntPtr pNull1, string lpszDevice,
        IntPtr
        pNull2, IntPtr lpInitData);

        // StartDocW - the Unicode version of StartDoc - to start a print job
        [DllImport("gdi32.dll", EntryPoint = "StartDocW", SetLastError = true,
        CharSet = CharSet.Unicode, ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
        public static extern Int32 StartDoc(IntPtr hDC, [In,
        MarshalAs(UnmanagedType.LPStruct)] DOCINFO di);

        // StartPage - starts a new page in a print job
        [DllImport("gdi32.dll", EntryPoint = "StartPage", SetLastError = true,
        CharSet = CharSet.Auto, ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
        public static extern Int32 StartPage(IntPtr hDC);

        // EndPage - ends the current page on a printer DC
        [DllImport("gdi32.dll", EntryPoint = "EndPage", SetLastError = true,
        CharSet = CharSet.Auto, ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
        public static extern Int32 EndPage(IntPtr hDC);

        // EndDoc - signals the end of a print job
        [DllImport("gdi32.dll", EntryPoint = "EndDoc", SetLastError = true,
        CharSet = CharSet.Auto, ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
        public static extern Int32 EndDoc(IntPtr hDC);

        // DeleteDC - to clean up our printer DC after the job is done
        [DllImport("gdi32.dll", EntryPoint = "DeleteDC", SetLastError = true,
        CharSet = CharSet.Auto, ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
        public static extern bool DeleteDC(IntPtr hDC);

        [DllImport("gdi32")]
        public static extern bool Rectangle(IntPtr hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);