PDF Печать через службу Windows с C # - PullRequest
7 голосов
/ 04 ноября 2019

Я использую этот код для печати файла PDF на локальном принтере с C # в службе Windows.

Process process = new Process();
PrinterSettings printerSettings = new PrinterSettings();

if (!string.IsNullOrWhiteSpace(basePrint))
   printerSettings.PrinterName = basePrint;

process.StartInfo.FileName = fileName;
process.StartInfo.Verb = "printto";
process.StartInfo.Arguments = "\"" + printerSettings.PrinterName + "\"";
process.Start();
process.WaitForInputIdle();

Все работает нормально, когда я настраиваю пользователя для запуска службы Windows.

Всякий раз, когда я запускаю этот код под учетными данными LocalSystem, я получаю сообщение об ошибке «Нет приложений, связанных с этой операцией», что обычно означает, что у меня нет программы, готовой работать с операцией печатифайл с расширением .pdf.

Моя проблема в том, что у меня есть программа (Foxit Reader) для выполнения этой операции, что подтверждается тем фактом, что этот код работает с определенным пользователем, установленным в службе, и что яЯ могу отправить файлы на принтер, щелкнув их правой кнопкой мыши и выбрав опцию печати. ​​

Могу ли я изменить что-либо, чтобы можно было печатать на локальном принтере из службы без определенного пользователя?

Ответы [ 4 ]

4 голосов
/ 11 ноября 2019

Я использовал pdfium для этой работы. С этим кодом файл PDF отправляется на принтер правильно, даже когда служба Windows работает под пользователем LocalService.

PrinterSettings printerSettings = new PrinterSettings()
{
    PrinterName = printerName,
    Copies = 1
};

PageSettings pageSettings = new PageSettings(printerSettings)
{
    Margins = new Margins(0, 0, 0, 0)
};

foreach (PaperSize paperSize in printerSettings.PaperSizes)
{
    if (paperSize.PaperName == "A4")
    {
        pageSettings.PaperSize = paperSize;
        break;
    }
}

using (PdfDocument pdfDocument = PdfDocument.Load(filePath))
{
    using (PrintDocument printDocument = pdfDocument.CreatePrintDocument())
    {
        printDocument.PrinterSettings = printerSettings;
        printDocument.DefaultPageSettings = pageSettings;
        printDocument.PrintController = (PrintController) new     StandardPrintController();
        printDocument.Print();
    }
}

Спасибо за ответы, ребята.

0 голосов
/ 13 ноября 2019

Может ли быть так, что PDF-приложение находится не в общесистемной переменной PATH, а только под вашим конкретным пользователем?

Я думаю, что ваша проблема возникает из-за того, что пользователь "локальной системы" ненайти правильное приложение, так что вам придется зарегистрировать его для него. Поскольку вы уже приняли другой ответ, я не буду уделять этому больше времени, но если есть дополнительные вопросы, пожалуйста, задавайте.

0 голосов
/ 08 ноября 2019

Возможно, вы можете выполнить свой рабочий код, но используя текущий активный токен пользователя сеанса (но без активного сеанса это не должно работать)

Для краткости этот код не скомпилируется: высначала нужно адаптировать и добавить P / Invoke .

Вы должны найти идентификатор активной сессии. Для локального открытого сеанса используйте это:

    [DllImport("wtsapi32.dll", SetLastError = true)]
    public static extern int WTSEnumerateSessions(
        IntPtr hServer,
        int Reserved,
        int Version,
        ref IntPtr ppSessionInfo,
        ref int pCount);

Затем найдите идентификатор открытого сеанса:

        var typeSessionInfo = typeof(WTSApi32.WTSSessionInfo);
        var sizeSessionInfo = Marshal.SizeOf(typeSessionInfo);
        var current = handleSessionInfo;
        for (var i = 0; i < sessionCount; i++)
        {
            var sessionInfo = (WTSApi32.WTSSessionInfo)Marshal.PtrToStructure(current, typeSessionInfo);
            current += sizeSessionInfo;
            if (sessionInfo.State == WTSApi32.WTSConnectStateClass.WTSActive)
                return sessionInfo.SessionID;
        }

Если не найдено, найдите сеанс rdp с помощью:

    [DllImport("kernel32.dll")]
    public static extern uint WTSGetActiveConsoleSessionId();

С этим получите токен

    private static IntPtr GetUserImpersonatedToken(uint activeSessionId)
    {
        if (!WTSApi32.WTSQueryUserToken(activeSessionId, out var handleImpersonationToken))
            Win32Helper.RaiseInvalidOperation("WTSQueryUserToken");

        try
        {
            return DuplicateToken(handleImpersonationToken, AdvApi32.TokenType.TokenPrimary);
        }
        finally
        {
            Kernel32.CloseHandle(handleImpersonationToken);
        }
    }

С его помощью вы сможете выполнить exe из службы локальной системы в открытом сеансе пользователя.

    public static void ExecuteAsUserFromService(string appExeFullPath, uint activeSessionId, bool isVisible = false, string cmdLine = null, string workDir = null)
    {
        var tokenUser = GetUserImpersonatedToken(activeSessionId);
        try
        {
            if (!AdvApi32.SetTokenInformation(tokenUser, AdvApi32.TokenInformationClass.TokenSessionId, ref activeSessionId, sizeof(UInt32)))
                Win32Helper.RaiseInvalidOperation("SetTokenInformation");

            ExecuteAsUser(tokenUser, appExeFullPath, isVisible, cmdLine, workDir);
        }
        finally
        {
            Kernel32.CloseHandle(tokenUser);
        }
    }

Теперь посмотрим, сможете ли вы адаптироватьВаш код для CreateProcessAsUSer(...)

    private static void ExecuteAsUser(IntPtr token, string appExeFullPath, bool isVisible, string cmdLine, string workDir)
    {
        PrepareExecute(appExeFullPath, isVisible, ref workDir, out var creationFlags, out var startInfo, out var procInfo);
        try
        {
            startInfo.lpDesktop = "WinSta0\\Default";
            var processAttributes = new AdvApi32.SecurityAttributes
            {
                lpSecurityDescriptor = IntPtr.Zero
            };
            var threadAttributes = new AdvApi32.SecurityAttributes
            {
                lpSecurityDescriptor = IntPtr.Zero
            };
            if (!AdvApi32.CreateProcessAsUser(token,
                appExeFullPath, // Application Name
                cmdLine, // Command Line
                ref processAttributes,
                ref threadAttributes,
                true,
                creationFlags,
                IntPtr.Zero,
                workDir, // Working directory
                ref startInfo,
                out procInfo))
            {
                throw Win32Helper.RaiseInvalidOperation("CreateProcessAsUser");
            }
        }
        finally
        {
            Kernel32.CloseHandle(procInfo.hThread);
            Kernel32.CloseHandle(procInfo.hProcess);
        }
    }

Надеюсь, этот код пригодится вам или кому-то еще!

0 голосов
/ 08 ноября 2019

Проблема может заключаться в том, что учетная запись SYSTEM (LocalSystem) имеет ограниченные возможности пользовательского интерфейса и, возможно, расширения оболочки или оболочки удалены или отключены. А глаголы - это возможность подсистемы оболочки, в частности, Проводника.

Вы можете вызвать программу вручную, чтобы проверить, так ли это, проблема безопасности или отсутствие данных профиля пользователя.

Для этого вам нужно покопаться в реестре, и вы обнаружите, что многие глаголы расширения выполнения оболочки имеют командную строку.

Например, найдите команду HKEY_CLASSES_ROOT.pdf \ shell \ printto \ и используйте еекоманда.

Кроме того, вы можете проверить, что учетная запись SYSTEM имеет доступ к этому и родительским ключам. (Редко бывает, но стоит проверить)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...