Как печатать на сетевой принтер через приложение, размещенное в IIS
У меня есть сценарий печати PDF (генерируется из потока) на сетевой принтер через приложение, размещенное в IIS. Я попытался с PrintDocument.Print(), и проблема, с которой я сталкиваюсь, заключается в следующем: 1. Документ попадает в очередь на задание печати размером 0 байт. 2. Документ попадает в очередь на задание на печать с именем владельца в качестве machine_name. Вот код, который я пытался использовать PdfiumViewer (для генерации PrintDocument из bytearray) и System.Drawing.Printing.PrintDocument:
public void SendPdfToPrinter(byte[] byteArray, string fileName, string printerNetworkPath)
{
using (Stream fileStream = new MemoryStream(byteArray)) //byte array for the file content
{
var printerSettings = new System.Drawing.Printing.PrinterSettings
{
PrinterName = printerNetworkPath, //this is the printer full name. i.e. \\10.10.0.12\ABC-XEROX-01
PrintFileName = fileName, //file name. i.e. abc.pdf
PrintRange = System.Drawing.Printing.PrintRange.AllPages,
};
printerSettings.DefaultPageSettings.Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0);
// Now print the PDF document
using (PdfiumViewer.PdfDocument document = PdfiumViewer.PdfDocument.Load(fileStream))
{
using (System.Drawing.Printing.PrintDocument printDocument = document.CreatePrintDocument())
{
printDocument.DocumentName = fileName;
printDocument.PrinterSettings = printerSettings;
printDocument.PrintController = new System.Drawing.Printing.StandardPrintController();
printDocument.Print();
}
}
2 ответа
Другое возможное и простое решение - настроить удостоверение пула приложений для пользователя / пользователя домена, у которого есть доступ / разрешение на печать на сетевых принтерах.
Для обеих проблем ответом является выдавать себя за пользователя, который выполняет печать.
В моем случае пул приложений работает под учетной записью LocalSystem, которая, очевидно, не является пользователем домена, а принтер открыт только для пользователя домена.
Примечание. Пул приложений -64-битный, если вы используете 32-битный, вы столкнетесь с другим набором проблем, который хорошо описан здесь: https://blogs.msdn.microsoft.com/winsdk/2015/05/19/printing-successfully-using-impersonation-from-a-32-bit-application-on-a-64-bit-system/
Ниже приведен код, который требуется для олицетворения пользователя домена:
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonation : IDisposable
{
private readonly SafeTokenHandle _handle;
private readonly WindowsImpersonationContext _context;
bool disposed = false;
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
public Impersonation(ImpersonateUserDetails user) : this(user.Domain, user.UserName, user.Password)
{ }
public Impersonation(string domain, string username, string password)
{
var ok = LogonUser(username, domain, password,
LOGON32_LOGON_INTERACTIVE, 0, 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; }
}