Impressão em PDF através do serviço Windows com C #

8

Estou usando esse código para imprimir um arquivo PDF em uma impressora local com C # dentro de um serviço do 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();

Tudo funciona bem quando eu configuro um usuário para executar o serviço do Windows.

Sempre que executo esse código com a credencial LocalSystem, recebo o erro "não há aplicativo associado a esta operação", o que geralmente indica que não tenho um programa pronto para lidar com a operação de impressão de um arquivo com a extensão .pdf .

Meu problema é que eu tenho o programa (Foxit Reader) para lidar com esta operação, como confirmado pelo fato de que esse código funciona com um usuário específico definido no serviço e que eu posso enviar arquivos para a impressora clicando com o botão direito do mouse. e selecionando a opção de impressão.

Há algo que eu possa alterar para poder imprimir em uma impressora local de dentro de um serviço sem um usuário específico?

Caio Sant'Anna
fonte
Que tipo de impressora é essa? Instalada localmente ou uma impressora de rede compartilhada via \\ nomedocomputador \ nome da impressora?
precisa saber é
É um Epson EcoTank L375 ( epson.com.jm/Support/Printers/All-In-Ones/L-Series/… ) instalado localmente.
Caio Sant'Anna

Respostas:

4

Acabei usando pdfium para fazer o trabalho. Com esse código, o arquivo PDF é enviado para a impressora corretamente, mesmo quando o serviço do Windows está sendo executado no usuário 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();
    }
}

Obrigado pelas respostas pessoal.

Caio Sant'Anna
fonte
0

O problema pode ser que a conta SYSTEM (LocalSystem) tenha recursos limitados da interface do usuário e, possivelmente, extensões de shell ou shell sejam removidas ou desativadas. E verbos são uma capacidade do subsistema shell, especificamente, o Explorer.

Você pode invocar o programa manualmente para ver se esse é o caso ou se é um problema de segurança ou falta de detalhes do perfil do usuário.

Para fazer isso, você precisa cavar o Registro e verá que muitos verbos de extensão de execução de shell têm uma linha de comando.

Por exemplo, localize HKEY_CLASSES_ROOT.pdf \ shell \ printto \ command e use esse comando.

Além disso, você pode verificar se a conta SYSTEM tem acesso a isso e às chaves pai. (Raramente, mas vale a pena conferir)

Bahram Ardalan
fonte
0

Talvez você possa executar seu código de trabalho, mas usando um token de usuário de sessão atualmente ativo (mas sem sessão ativa, isso não deve funcionar)

Por uma questão de brevidade, esse código não será compilado: você deve adaptar e adicionar alguns P / Invoke primeiro.

Você precisa encontrar o ID da sessão ativa. Para uma sessão aberta local, use este:

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

Em seguida, procure um ID de sessão aberta:

        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;
        }

Se não encontrado, pesquise uma sessão de PDR com:

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

Com isso, pegue um token

    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);
        }
    }

Com isso, você pode executar um exe do serviço Sistema Local, em uma sessão de usuário aberta.

    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);
        }
    }

Agora veja se você pode adaptar seu código a 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);
        }
    }

Esperando que este código seja útil para você ou para outra pessoa!

Dmo
fonte
0

Pode ser que o aplicativo PDF não esteja na variável PATH em todo o sistema, mas apenas no seu usuário específico?

Penso que o seu problema ocorre porque o utilizador do "sistema local" não encontra uma aplicação adequada, pelo que terá de o registar para ele. Como você já aceitou outra resposta, não vou dedicar mais tempo a isso, mas se houver mais perguntas, pergunte.

tillhoff
fonte