Impersonation fails for printing on network printer.
Closed this issue · 1 comments
I'm trying to access the network printers by doing impersonation (using this code) to print a pdf document from IIS (wcf application). Since my wcf application run under 'LocalSystem' app pool identity, i need to do the impersonation. But Printing doesn't work with this impersonation, even I can't list out the network printers also.
If I change the logonType to 'LOGON32_LOGON_INTERACTIVE' and logonProvider as 'LOGON32_PROVIDER_DEFAULT' then I can list out the network printers but when I send the document to the printer it doesn't print. it shows with Size empty in the print queue. Any Idea please???
Impersonation Code:
` [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonation : IDisposable
{
private readonly SafeTokenHandle _handle;
private readonly WindowsImpersonationContext _context;
bool disposed = false;
// constants from winbase.h
const int LOGON32_LOGON_INTERACTIVE = 2;
const int LOGON32_LOGON_NETWORK = 3;
const int LOGON32_LOGON_BATCH = 4;
const int LOGON32_LOGON_SERVICE = 5;
const int LOGON32_LOGON_UNLOCK = 7;
const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_PROVIDER_WINNT35 = 1;
const int LOGON32_PROVIDER_WINNT40 = 2;
const int LOGON32_PROVIDER_WINNT50 = 3;
public Impersonation(ImpersonateUserDetails user) : this(user.Domain, user.UserName, user.Password)
{ }
public Impersonation(string domain, string username, string password)
{
// if domain name was blank, assume local machine
if (string.IsNullOrEmpty(domain))
domain = System.Environment.MachineName;
var ok = LogonUser(username, domain, password,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out this._handle);
if (!ok)
{
var errorCode = Marshal.GetLastWin32Error();
throw new ApplicationException(string.Format("Could not impersonate the elevated user. LogonUser returned error code {0}.", errorCode));
}
this._context = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
this._context.Dispose();
this._handle.Dispose();
}
disposed = true;
}
~Impersonation()
{
Dispose(false);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);
sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true) { }
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
}
public class ImpersonateUserDetails
{
public string UserName { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
}`
Printing code to Network Printer:
using (new Impersonation(domain, username, password)) { PrinterUtility pu = new PrinterUtility(); pu.Print(fileName, "TestFile.pdf", printerName); }
PrinterUtility Class:
` using System;
using System.Drawing.Printing;
using System.IO;
using System.Drawing;
using System.Text;
namespace NetworkPrintWCF
{
public class PrinterUtility
{
private Font printFont;
private StreamReader streamToPrint;
private void pd_PrintPage(object sender, PrintPageEventArgs ev)
{
float linesPerPage = 0;
float yPos = 0;
int count = 0;
float leftMargin = ev.MarginBounds.Left;
float topMargin = ev.MarginBounds.Top;
String line = null;
// Calculate the number of lines per page.
linesPerPage = ev.MarginBounds.Height /
printFont.GetHeight(ev.Graphics);
// Iterate over the file, printing each line.
while (count < linesPerPage &&
((line = streamToPrint.ReadLine()) != null))
{
yPos = topMargin + (count * printFont.GetHeight(ev.Graphics));
ev.Graphics.DrawString(line, printFont, Brushes.Black,
leftMargin, yPos, new StringFormat());
count++;
}
// If more lines exist, print another page.
if (line != null)
ev.HasMorePages = true;
else
ev.HasMorePages = false;
}
// Print the file.
public void Print(string filePath, string fileName, string printerNetworkPath)
{
try
{
streamToPrint = new StreamReader(filePath);
try
{
var printerSettings = new PrinterSettings
{
PrinterName = printerNetworkPath,
PrintFileName = fileName,
PrintRange = PrintRange.AllPages,
};
printerSettings.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0);
printFont = new Font("Arial", 10);
PrintDocument pd = new PrintDocument();
pd.DocumentName = fileName;
pd.PrinterSettings = printerSettings;
//pd.PrintController = new StandardPrintController();
pd.PrintPage += new PrintPageEventHandler(pd_PrintPage);
// Print the document.
pd.Print();
}
finally
{
streamToPrint.Close();
}
}
catch
{
throw;
}
}
}
}`
I'm sure this is the problem with impersonation. because If I change my application's app pool identity to a domain user (same domain user credential I was trying to use to impersonate) then printing works fine.
Web Server Environment: Windows Server 2012 R2, IIS 8.5.
But with this impersonationated code, printing fails. Document sows up in print queue with NO size (kindly refer attached image)
Sorry, I have no idea. It seems like you are using your own code, not mine. Do you get the same results if you use my library?
When I wrote this library (years ago), I used it for connecting to a SQL server under specific domain credentials. Others have used it for various other things. If it works with LogonUser
in a C++ app, it should work with SimpleImpersonation in .NET. I just wrap the LogonUser
function. How it works under the hood is beyond me.
Also, from a purely architectural point of view, I would not print directly from server-side asp.net code. Consider writing to a queue or database table, and having a separate background service read from there to handle printing. If that is out of the question, then consider using HostingEnvironment.QueueBackgroundWorkItem
, introduced in .NET 4.5.2.