Uncategorized

Need to print from a x64 machine? Can you wait 60 seconds?

I guess some of you might have developed a web application which, among other functionalities, prints some kind of report; and sooner or later you might consider to move the application to a 64 bit machine. At this is what this customer did.

They had this ASP.NET application which allows the user to run some queries on a backend database and then print the result on a network printer connected to the web server; under some circumstances the application was hanging for 60 seconds (always 60 seconds) before timing out; of course everything was running fine on a 32 bit machine (i.e. we never managed to reproduce the issue there).

At the beginning we thought to some kind of network timeout on the customer’s network or a configuration problem on the server (the application is quite complex so was not possible to have a repro to run and debug on my machine), and this was partially confirmed by a hang dump which showed some error messages about the printer, but this was not consistent enough to get a clue. What was consistent was the use of winspool.dll

ntdll!NtDelayExecution(void)+0x15
kernel32!SleepEx(unsigned long dwMilliseconds = 0x3e8, int bAlertable = 0)+0x68
kernel32!Sleep(unsigned long dwMilliseconds = 0x3e8)+0xf
winspool!ConnectToLd64In32ServerWorker(void ** hProcess = 0x7309214c, int bThread = 1)+0x33e
winspool!ConnectToLd64In32Server(void ** hProcess = 0x7309214c, int bThread = 1)+0x1b
winspool!DeviceCapabilitiesWThunk(unsigned short * pDevice = 0x790d8858, unsigned short * pPort = 0x0ef69df8, unsigned short fwCapability = 0x12, unsigned short * pOutput = 0x00000000, struct _devicemodeW * pDevMode = 0x00000000)+0x77
CLRStub[StubLinkStub]@1af41e5e(<Win32 error 0n318>)
System_Drawing_ni!System.Drawing.Printing.PrinterSettings.FastDeviceCapabilities(<HRESULT 0x80004001>)+0x2e
System.Drawing.Printing.PrinterSettings.DeviceCapabilities(<HRESULT 0x80004001>)+0x66
System_Drawing_ni!System.Drawing.Printing.PrinterSettings.get_IsValid(<HRESULT 0x80004001>)+0x21
App_Web_nqftqomb!_Default.Button4_Click(<HRESULT 0x80004001>)+0xf2

As you can see, after the click on the button to print the report we’re going down to the OS for the actual printing, we’re passing through the ConnectToLd64In32ServerWorker method and then we’re waiting for 1 second (dwMilliseconds = 0x3e8 = 1000 milliseconds = 1 second). The next logical step has been to have a look at the source code for that particular method in winspool and found that under under some circumstances we try to connect to an RPC resource and we retry to 60 seconds before exiting to avoid an infinite hang. Good, now we only have to understand why using NETWORK SERVICE works fine while impersonating the logged on user fails (clearly some kind of missing permission).

Unfortunately the final customer was not interested in spending more time investigating the issue but they wanted a working solution right away, so we decided to use impersonation by code to switch to NETWORK SERVICE when ready to print and then switch back to the logged on user; we made a few changes to the same code I already used for another case (here) to do the trick:

protected void Button1_Click(object sender, EventArgs e)
{
    //This should be the authenticated user
    Label1.Text = "Process running as " + WindowsIdentity.GetCurrent().Name + " (before impersonating)";

    //Save a reference to the authenticated user token for later
    WindowsPrincipal p = (WindowsPrincipal)HttpContext.Current.User;
    WindowsIdentity id = (WindowsIdentity)p.Identity;

    //Revert to the worker process account (default is NETWORK SERVICE)
    RevertToSelf();
    Label3.Text = "Process running as " + WindowsIdentity.GetCurrent().Name + " (after impersonating)";

    try
    {
        //Print an to whatever you need to do under the NETWORK SERVICE context
    }
    catch (Exception ex)
    {
        Label2.Text = ex.Message;
    }

    //Impersonate back the authenticatd user
    WindowsImpersonationContext wic = id.Impersonate();
    Label4.Text = "Process running as " + WindowsIdentity.GetCurrent().Name + " (reverting back)";

    //VERY IMPORTANT: ALWAYS REMEMBER TO CALL THE UNDO METHOD WHEN DONE!
    wic.Undo();
}

While it’s a shame that we didn’t have the opportunity to fully understand what was going wrong, if you’ll ever find yourself in this situation you at least should have a solution ready to have you application up and running quickly; but of course if you what to understand more feel free to open a call with CSS! ?

Carlo

Quote of the day:

Inspiration is wonderful when it happens, but the writer must develop an approach for the rest of the time… The wait is simply too long. – Leonard Bernstein

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.